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.
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.
iptables -N UDPCHECK
iptables -A UDPCHECK -m state --state INVALID -j LOGDROP
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.
iptables -N UDP-IN-REQ
iptables -N UDP-OUT-RESP
iptables -N UDP-OUT-REQ
iptables -N UDP-IN-RESP
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.
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.
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
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.
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
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.
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.
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.
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.
iptables -N ICMP-IN-REQ
iptables -N ICMP-OUT-RESP
iptables -N ICMP-OUT-REQ
iptables -N ICMP-IN-RESP
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.
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
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.
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
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.
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
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.
iptables -A INPUT --in-interface lo -j ACCEPT
iptables -A OUTPUT --out-interface lo -j ACCEPT
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.
iptables -A INPUT -m addrtype --dst-type BROADCAST -j DROP
iptables -A OUTPUT -m addrtype --dst-type BROADCAST -j LOGDROP
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.
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.