Beyond TCP

UDP based protocols

In the previous chapters we build a script to configure our firewall to allow access to TCP based services served from both the local machine and remote systems. In this section we shall move beyond TCP based protocols to cover UDP based protocols.

Filtering invalid packets

Lets begin, as before, by creating a chain which we can use to detect and drop any UDP packets which we don't like the look of. We shan't add any rules here, apart from the built-in INVALID state tests, but we shall come back to it when we discuss some of the more advanced filtering options later. Add the code below to the end of the script as usual.

/usr/local/sbin/config-firewall
iptables -N UDPCHECK
iptables -A UDPCHECK -m state --state INVALID -j LOGDROP

Organising our rules

So that our rules can be more easily identified we shall be grouping them in the same way we did with the TCP rules we created earlier. To accomplish this we shall use the code below.

/usr/local/sbin/config-firewall
iptables -N UDP-IN-REQ
iptables -N UDP-OUT-RESP
iptables -N UDP-OUT-REQ
iptables -N UDP-IN-RESP

Adding the rules

Allowing UDP traffic is an almost identical procedure to that which we followed earlier to allow TCP traffic. As DNS is probably the most critical UDP based protocol we have given an example, below, for allowing DNS requests to leave the system and their replies to enter the system.

/usr/local/sbin/config-firewall
iptables -A UDP-OUT-REQ -p udp --dport domain -m state --state NEW         -j ACCEPT
iptables -A UDP-OUT-REQ -p udp --dport domain -m state --state ESTABLISHED -j ACCEPT
iptables -A UDP-IN-RESP -p udp --sport domain -m state --state ESTABLISHED -j ACCEPT

As you can see the rules are almost identical to their TCP counterparts. The only differences are that we have added them to the UDP chains and used the -p udp match specification instead of -p tcp.

If you are running a DNS server then you will need to reverse the direction part of the chain specifier as we did before when dealing with TCP rules. For your convenience the required code is given below.

/usr/local/sbin/config-firewall
iptables -A UDP-IN-REQ   -p udp --dport domain -m state --state NEW         -j ACCEPT
iptables -A UDP-IN-REQ -p udp --dport domain -m state --state ESTABLISHED -j ACCEPT
iptables -A UDP-OUT-RESP -p udp --sport domain -m state --state ESTABLISHED -j ACCEPT

Connecting it all together

Now that we have rules to allow UDP traffic in to and out of the system all that remains is to connect them to the existing infrastructure in the same way as we did with the TCP rules earlier. This is accomplished with the following code.

/usr/local/sbin/config-firewall
iptables -N UDP-IN-FILTER
iptables -A UDP-IN-FILTER -j UDPCHECK
iptables -A UDP-IN-FILTER -j UDP-IN-REQ
iptables -A UDP-IN-FILTER -j UDP-IN-RESP

iptables -A INPUT -p udp -j UDP-IN-FILTER

iptables -N UDP-OUT-FILTER
iptables -A UDP-OUT-FILTER -j UDPCHECK
iptables -A UDP-OUT-FILTER -j UDP-OUT-RESP
iptables -A UDP-OUT-FILTER -j UDP-OUT-REQ

iptables -A OUTPUT -p udp -j UDP-OUT-FILTER

ICMP messages

The only protocol which we haven't covered so far is the Internet Control Message Protocol, more commonly referred to as ICMP. As you have probably guessed from the name the ICMP protocol is used to notify hosts of error conditions as well as, in the case of the source-quench message for example, exert a limited level of control over hosts on the Internet. For this reason the ICMP protocol is often used by intruders to map networks as well as perform denial of service attacks. It is, therefore, a good idea to limit the ICMP messages which can be sent and received by both the local machine and any networks to which it is providing Internet connectivity.

Filtering invalid packets

As usual we shall start by creating a chain to filter packets which are probably malicious and require no further examination before we can log and drop them.

/usr/local/sbin/config-firewall
iptables -N ICMPCHECK
iptables -A ICMPCHECK -p icmp --fragment -j LOGDROP
iptables -A ICMPCHECK -m state --state INVALID -j LOGDROP

The first rule in the above chain checks to see if the fragment flag is set. As ICMP packets are almost never large enough to cause genuine fragmentation this is almost certainly an attempt at something malicious and should therefore be logged and dropped. The second rule matches ICMP responses which are are arriving claiming to relate to connections that the conntrack system knows nothing about. These are, again, almost certainly bogus and should therefore be logged and dropped without further ado.

Organising our rules

As you are probably expecting, the next step is to create chains to allow us to organise our rules into their respective direction and state classes.

/usr/local/sbin/config-firewall
iptables -N ICMP-IN-REQ
iptables -N ICMP-OUT-RESP
iptables -N ICMP-OUT-REQ
iptables -N ICMP-IN-RESP

Adding the rules

One of the most common uses for the ICMP protocol is the ping application. This application sends ICMP echo-request messages and waits for the resulting echo-response messages, or lack thereof. As this application is extremely useful for fault finding I have decided to allow its use in both directions. The following rules accomplish this.

/usr/local/sbin/config-firewall
iptables -A ICMP-OUT-REQ -p icmp --icmp-type echo-request -j ACCEPT
iptables -A ICMP-IN-RESP -p icmp --icmp-type echo-reply -m state --state ESTABLISHED -j ACCEPT

iptables -A ICMP-IN-REQ -p icmp --icmp-type echo-request -j ACCEPT
iptables -A ICMP-OUT-RESP -p icmp --icmp-type echo-reply -m state --state ESTABLISHED -j ACCEPT
Information:
As you can see we can still make use of the conntrack subsystem to ensure that we only allow echo-reply messages which are genuinely arriving in response to echo-request messages which we sent and vice versa.
 

We mentioned earlier that the ICMP protocol is also used to signal error conditions which have occurred during communications. Some of these messages can be very useful to help diagnose and correct error conditions. For this reason we probably want to allow at least the message types given below into our network. Again we use the conntrack subsystem to ensure that we are only receiving ICMP errors which are in response to an ICMP message we sent or are related to a connection on a different protocol.

/usr/local/sbin/config-firewall
iptables -A ICMP-IN-RESP -p icmp --icmp-type destination-unreachable -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A ICMP-IN-RESP -p icmp --icmp-type time-exceeded -m state --state RELATED,ESTABLISHED -j ACCEPT

Connecting it all together

All that remains to be done now is to link the chains we have just created to the INPUT and OUTPUT chains as we did before.

/usr/local/sbin/config-firewall
iptables -N ICMP-IN-FILTER
iptables -A ICMP-IN-FILTER -j ICMPCHECK
iptables -A ICMP-IN-FILTER -j ICMP-IN-REQ
iptables -A ICMP-IN-FILTER -j ICMP-IN-RESP

iptables -A INPUT -p icmp -j ICMP-IN-FILTER

iptables -N ICMP-OUT-FILTER
iptables -A ICMP-OUT-FILTER -j ICMPCHECK
iptables -A ICMP-OUT-FILTER -j ICMP-OUT-RESP
iptables -A ICMP-OUT-FILTER -j ICMP-OUT-REQ

iptables -A OUTPUT -p icmp -j ICMP-OUT-FILTER

The loopback interface

One last task we must complete before we can change the policies of the INPUT and OUTPUT chains to DROP is to allow traffic to traverse the loopback interface. This interface is often used to connect internal processes together without exposing them to the rest of the network. A common example of such a service is a local DNS cache which listens for locally generated requests on the loopback interface.

To accomplish this in a secure way, by granting access to just the loopback interface as opposed to allowing all packets with a local source and destination address, we shall need to use two more built-in match specifiers. These match the incoming and outgoing interface of the packet respectively. Both of these matchers require, as their only parameter, a single interface name which will be used to match against the interface of the packet in question.

/usr/local/sbin/config-firewall
iptables -A INPUT  --in-interface  lo -j ACCEPT
iptables -A OUTPUT --out-interface lo -j ACCEPT

Broadcast packets

If you are on a network which is not extremely tightly controlled there is a good chance that the current rules will generate quite a large number of logged events per hour. The main reason for this is that you will encounter a surprisingly large number of broadcast packets. Far more than I would have ever imagined in fact.

As we are following the philosophy of "close everything then open what you need" we should already have ports opened for any broadcasts which we are actually interested in. Any other broadcast packets are of little interest to us therefore and are probably not worth logging as they are very unlikely to represent an attack.

We can make use of the addrtype matcher to construct a match specifier which will match all packets which have a broadcast address as their destination. An example of such a rule is given below. We could have created a separate chain to handle the dropping of broadcast packets but as it is only one rule it is probably more efficient to just add it twice.

Information:
As a courtesy to other users of the network we are going to block any outgoing broadcasts too. If you use a protocol which needs them they should already have rules of their own so any outgoing broadcast packets are therefore logged before being dropped as they may be suspicious.
 
/usr/local/sbin/config-firewall
iptables -A INPUT  -m addrtype --dst-type BROADCAST -j DROP
iptables -A OUTPUT -m addrtype --dst-type BROADCAST -j LOGDROP
Warning:
As usual when adding DROP rules you should be very careful that no required traffic will be discarded. A common protocol which uses broadcasts, and should therefore be ACCEPTed if used, is DHCP. You have been warned.
 

Finishing up

You should now have a firewall configuration script which will allow communications using only the protocols you actually require. At least you would have if the policy of the INPUT and OUTPUT chains wasn't still set to ACCEPT.

Before we have a secure configuration we must ensure that all packets are dropped. It would be nice if they were logged as well, at least while we are debugging our rules. The obvious solution would be to set the policy of the INPUT and OUTPUT chains to LOGDROP. This is, unfortunately, not possible as a chain's policy target must be a built-in terminal target. We can, however, achieve the same result using an unconditional jump to the LOGDROP chain as the last rule in our chains.

/usr/local/sbin/config-firewall
iptables -A INPUT  -j LOGDROP
iptables -A OUTPUT -j LOGDROP

iptables -P INPUT DROP
iptables -P OUTPUT DROP

We have set the policy of the INPUT and OUTPUT chains to DROP as a safeguard in case the jump to DROP should ever be accidentally removed from the LOGDROP chain or otherwise rendered impotent. They are also included as a safety measure so that when you remove the jump to the LOGDROP chain, after final testing is complete, you don't forget to include them.

Warning:
Earlier we warned you that if you are configuring the machine remotely using ssh you should comment out the jump to the DROP target in the LOGDROP chain. You should probably comment out the above DROP policy lines too until you are sure that you can connect without seeing anything in the logs. Don't forget to uncomment these lines before you put the script in to production though!