sysadmin

Defensive Measures

Firewalls

Firewalls are a classic defensive measure. Designed as a tool to limit inbound or outbound connections, firewalls are traditionally rule based. We will primarily be discussing the Linux iptables instantiation of a firewall, mainly due to the ease of access – it’s on the Kali VM we have been using all term.

At the end of the day, firewalls are nothing but sets of rules. These rules govern which packets are kept, and which packets get dropped like a bad habit. One of the most important things to keep in mind is that order matters!

Firewall rules

As mentioned above, firewalls are simply a set of rules. If you’d like to see the current rule-set, execute the below

┌─(ROOT@kali478-0:pts/4)───────────────────────────────────────────────────────────────────(~/cs478)─┐
└─(2:12:43:#)── iptables --list                                                        ──(Fri,Feb14)─┘
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

As you can see, within Kali, the defaults are empty. If you want to know what else iptables can do, take a look at the help:

┌─(ROOT@kali478-0:pts/4)───────────────────────────────────────────────────────────────────(~/cs478)─┐
└─(2:12:43:#)── iptables --help                                                        ──(Fri,Feb14)─┘
iptables v1.8.3

Usage: iptables -[ACD] chain rule-specification [options]
	iptables -I chain [rulenum] rule-specification [options]
	iptables -R chain rulenum rule-specification [options]
	iptables -D chain rulenum [options]
	iptables -[LS] [chain [rulenum]] [options]
	iptables -[FZ] [chain] [options]
	iptables -[NX] chain
	iptables -E old-chain-name new-chain-name
	iptables -P chain target [options]
	iptables -h (print this help information)

Commands:
Either long or short options are allowed.
  --append  -A chain		Append to chain
  --check   -C chain		Check for the existence of a rule
  --delete  -D chain		Delete matching rule from chain
  --delete  -D chain rulenum
				Delete rule rulenum (1 = first) from chain
  --insert  -I chain [rulenum]
				Insert in chain as rulenum (default 1=first)
  --replace -R chain rulenum
				Replace rule rulenum (1 = first) in chain
  --list    -L [chain [rulenum]]
				List the rules in a chain or all chains
  --list-rules -S [chain [rulenum]]
				Print the rules in a chain or all chains
  --flush   -F [chain]		Delete all rules in  chain or all chains
  --zero    -Z [chain [rulenum]]
				Zero counters in chain or all chains
  --new     -N chain		Create a new user-defined chain
  --delete-chain
	     -X [chain]		Delete a user-defined chain
  --policy  -P chain target
				Change policy on chain to target
  --rename-chain
	     -E old-chain new-chain
				Change chain name, (moving any references)
Options:
    --ipv4	-4		Nothing (line is ignored by ip6tables-restore)
    --ipv6	-6		Error (line is ignored by iptables-restore)
[!] --proto	-p proto	protocol: by number or name, eg. 'tcp'
[!] --source	-s address[/mask][...]
				source specification
[!] --destination -d address[/mask][...]
				destination specification
[!] --in-interface -i input name[+]
				network interface name ([+] for wildcard)
 --jump	-j target
				target for rule (may load target extension)
  --goto      -g chain
			       jump to chain with no return
  --match	-m match
				extended match (may load extension)
  --numeric	-n		numeric output of addresses and ports
[!] --out-interface -o output name[+]
				network interface name ([+] for wildcard)
  --table	-t table	table to manipulate (default: 'filter')
  --verbose	-v		verbose mode
  --wait	-w [seconds]	maximum wait to acquire xtables lock before give up
  --wait-interval -W [usecs]	wait time to try to acquire xtables lock
				default is 1 second
  --line-numbers		print line numbers when listing
  --exact	-x		expand numbers (display exact values)
[!] --fragment	-f		match second or further fragments only
  --modprobe=<command>		try to insert modules using this command
  --set-counters PKTS BYTES	set the counter during insert/append
[!] --version	-V		print package version.

So let’s add some rules:

❯ iptables -A OUTPUT -p tcp -d ubuntu.com -j ACCEPT
❯ iptables -A OUTPUT -p tcp -d ca.archive.ubuntu.com -j ACCEPT
❯ iptables -A OUTPUT -p tcp --dport 80 -j DROP
❯ iptables -A OUTPUT -p tcp --dport 443 -j DROP
❯ iptables -A INPUT -p tcp -s 172.16.0.45 --dport 22 -j ACCEPT
❯ iptables -A INPUT -p tcp -s 0.0.0.0/0 --dport 22 -j DROP

What do these rules do? Let’s break them down:

  1. Allow all traffic to ubuntu.com
  2. Allow all traffic to ca.archive.ubuntu.com
  3. Drop all traffic destined for port 80
  4. Drop all traffic destined for port 443
  5. Allow traffic from 172.16.0.45 to port 22
  6. Drop all traffic from any other source to port 22

So we can SSH in, we can update the system, but we can’t browse the web. This is a very basic example of how iptables can be used to control traffic.

Rule processing

Rules are processed in numerical order, from first to last. Processing stops as soon as a rule is matched – ORDER MATTERS! There are ways to move rules around in the ipchains, but it’s better to just enter them in the correct order.

Intrusion Detection/Prevention Systems

Suricata is an open source (with commercial support) network based intrusion detection/prevention system (NIDS/NIPS). Much like the Snort tool, it is a production grade NIPS that has significant community support. For our purposes, Suricata is the example used due to its support of rule expansions via the Lua scripting language. Snortv3 also now supports Lua scripts in rules, but is not well documented (to my knowledge).

Suricata rules

From the Suricata manual:

A rule/signature consists of the following:

* The action, that determines what happens when the signature matches
* The header, defining the protocol, IP addresses, ports and direction of the rule.
* The rule options, defining the specifics of the rule.
#action protocol IPs ports -> IPs ports (trigger conditions)

drop tcp 10.0.0.0/8 any -> $HOME_NET any (msg:"dropping all traffic from non-routable IPs"; sid:20201234; rev:2;)

The above has both the general format, as well as an example rule. sid is required, while rev is recommended.

Built-in rules

Suricata comes with a significant batch of pre-built rules for a lot of the common network packets you might want to filter out. These include unwanted pings, network scan requests, ICMP traffic at all, filtering for known exploit traffic, etc. Further, there are some packet “classes” you can use in rules that encapsulate fairly complex rule logic.

Custom rules

Sometimes though, the built in rules just won’t cut it. Maybe there’s a new exploit in the wild. Maybe you want to alert on specific actions from smb. Maybe you just want to explore. There are several ways to add custom rules:

The above list is not exhaustive, and I want us to focus primarily on the final point. Lua gives us the ability to perform complex logic on our packet structure or the content of the packet. It allows for things like detecting a mismatch between reported and actual length (remember that, it’ll be useful soon). The below verifies that reported length is the same as actual length.

 1 function init (args)
 2     local needs = {}
 3     needs["payload"] = tostring(true)
 4     return needs
 5 end
 6 
 7 function string.tohex(str)
 8     return (str:gsub('.', function (c)
 9         return string.format('%02X ', string.byte(c))
10     end))  
11 end
12 
13 function match(args)
14     local b = args['payload']
15     if b == nil then
16         print ("Payload buffer empty! Aborting...")
17         return 0
18     end
19     print (string.tohex(b))
20     -- DNS RFC specifies length is reported in the first two bytes.
21     dns_size_high_byte = b:byte(1)
22     dns_size_low_byte = b:byte(2)
23     dns_size = tonumber(dns_size_high_byte) * 256 + tonumber(dns_size_low_byte)
24     -- check to ensure reported lenghth is same as actual length.  Subtract 2 for length field
25     if dns_size ~= string.len(b)-2 then
26             return 1
27     end
28     return 0
29 end

Lines 1-5 are required, while lines 7-11 are useful for debugging purposes. On a failed test, return 1 to trigger the rule. On a passing test, return 0 to skip this rule. Pay attention to lines 21-23 – this is how you pull out bytes and combine into a 16-bit int.

Optional Additional Resources

If this topic interests you, then you might find the following optional resources useful.

There exist some operating systems that are designed to be firewalls. These are often referred to as “firewall distros”. Some (but certainly not all) of the more popular ones are:

The first two are BSD based, while the rest are Linux based. Their capabilities vary, but they all offer, at minimum, a firewall and intrusion detection/prevention system. They also often have VPN capabilities and a user-friendly web interface for configuration.