Packet Filtering

This article explains packets, packet headers, and packet filtering. What can the option of allowing or disallowing packets be based on? What are the weaknesses and advantages of packet filtering? After you read, you should be able to describe packets and packet headers.

5. Iptables


In order to use iptables, it must be enabled in the kernel. I have added iptables as modules (the iptables command will load them as they are needed) and recompiled my kernel (but you may want to compile iptables in, if you intend to disable Loadable Kernel Modules as discussed previously). For more information on how to configure your kernel for iptables go to the Iptables Tutorial Chapter 5: Preparations. After you have compiled your new kernel (or while compiling the kernel), you must add the iptables command. Just emerge iptables and it should work.

Now test that it works by running iptables -L. If this fails something is wrong and you have to check you configuration once more.

Iptables is the new and heavily improved packet filter in the Linux 2.4.x kernel. It is the successor of the previous ipchains packet filter in the Linux 2.2.x kernel. One of the major improvements is that iptables is able to perform stateful packet filtering. With stateful packet filtering, it is possible to keep track of each established TCP connection.

A TCP connection consists of a series of packets containing information about source IP address, destination IP address, source port, destination port, and a sequence number so the packets can be reassembled without losing data. TCP is a connection-oriented protocol, in contrast to UDP, which is connectionless.

By examining the TCP packet header, a stateful packet filter can determine if a received TCP packet is part of an already established connection or not and decide either to accept or drop the packet.

With a stateless packet filter it is possible to fool the packet filter into accepting packets that should be dropped by manipulating the TCP packet headers. This could be done by manipulating the SYN flag or other flags in the TCP header to make a malicious packet appear to be a part of an established connection (since the packet filter itself does not do connection tracking). With stateful packet filtering it is possible to drop such packets, as they are not part of an already established connection. This will also stop the possibility of "stealth scans", a type of port scan in which the scanner sends packets with flags that are far less likely to be logged by a firewall than ordinary SYN packets.

Iptables provides several other features like NAT (Network Address Translation) and rate limiting. Rate limiting is extremely useful when trying to prevent certain DoS (Denial of Service) attacks like SYN floods.

A TCP connection is established by a so-called three-way handshake. When establishing a TCP connection the client-side sends a packet to the server with the SYN flag set. When the server-side receives the SYN packet it responds by sending a SYN+ACK packet back to the client-side. When the SYN+ACK is received, the client-side responds with a third ACK packet in effect acknowledging the connection.

A SYN flood attack is performed by sending the SYN packet but failing to respond to the SYN+ACK packet. The client-side can forge a packet with a fake source IP address because it does not need a reply. The server-side system will add an entry to a queue of half-open connections when it receives the SYN packet and then wait for the final ACK packet before deleting the entry from the queue. The queue has a limited number of slots and if all the slots are filled it is unable to open any further connections. If the ACK packet is not received before a specified timeout period the entry will automatically be deleted from the queue. The timeout settings vary but will typically be 30-60 seconds or even more. The client-side initiates the attack by forging a lot of SYN packets with different source IP addresses and sends them to the target IP address as fast as possible and thereby filling up the queue of half-open connections and thus preventing other clients from establishing a legitimate connection with the server.

This is where the rate limit becomes handy. It is possible to limit the rate of accepted SYN packets by using the -m limit --limit 1/s. This will limit the number of SYN packets accepted to one per second and therefore restricting the SYN flood on our resources.


Note
Another option for preventing SYN floods are SYN cookies, which allow your computer to respond to SYN packets without filling space in the connection queue. SYN cookies can be enabled in the Linux kernel configuration, but they are considered experimental at this time.

Now, some practical stuff!

When iptables is loaded in the kernel, it has 5 hooks where you can place your rules. They are called INPUT, OUTPUT, FORWARD, PREROUTING, and POSTROUTING. Each of these is called a chain and consists of a list of rules. Each rule says if the packet header looks like this, then here is what to do with the packet. If the rule does not match the packet the next rule in the chain is consulted.

You can place rules directly in the 5 main chains or create new chains and add them as a rule to an existing chain. Iptables supports the following options:

First, we will try to block all ICMP packets to our machine, just to get familiar with iptables.

Block all ICMP packets:

root #iptables -A INPUT -p icmp -j DROP


First we specify the chain our rule should be appended to, then the protocol of the packets to match, and finally the target. The target can be the name of a user specified chain or one of the special targets ACCEPTDROPREJECTLOGQUEUE, or MASQUERADE. In this case we use DROP, which will drop the packet without responding to the client.


Note
The LOG target is what's known as "non-terminating". If a packet matches a rule with the LOG target, rather than halting evaluation, the packet will continue to be matched to further rules. This allows you to log packets while still processing them normally.

Now try ping localhost. You will not get any response, since iptables will drop all incoming ICMP messages. You will also not be able to ping other machines, since the ICMP reply packet will be dropped as well. Now flush the chain to get ICMP flowing again:

root #iptables -F

Now, lets look at the stateful packet filtering in iptables. If we wanted to enable stateful inspection of packets incoming on eth0 we would issue the command:

root #iptables -A INPUT -i eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT

This will accept any packet from an already established connection or related in the INPUT chain. And you could drop any packet that is not in the state table by issuing iptables -A INPUT -i eth0 -m state --state INVALID -j DROP just before the previous command. This enables the stateful packet filtering in iptables by loading the extension "state". If you wanted to allow others to connect to your machine, you could use the flag --state NEW. Iptables contains some modules for different purposes. Some of them are:

Module/Match Description Extended options
mac Matching extension for incoming packets mac address. --mac-source
state Enables stateful inspection --state (states are ESTABLISHED,RELATED, INVALID, NEW)
limit Rate matching limiting --limit, --limit-burst
owner Attempt to match various characteristics of the packet creator --uid-owner userid --gid-owner groupid --pid-owner processid --sid-owner sessionid
unclean Various random sanity checks on packets Example


Let's try to create a user-defined chain and apply it to one of the existing chains.

First, create a new chain with one rule:

root #iptables -X mychain

root #iptables -N mychain

root #iptables -A mychain -i eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT

The default policy is all outgoing traffic is allowed. Incoming is dropped:

root #iptables -P OUTPUT ACCEPT

root #iptables -P INPUT DROP

And add it to the INPUT chain:

root #iptables -A INPUT -j mychain

By applying the rule to the input chain we get the policy: All outgoing packets are allowed and all incoming packets are dropped.

One can find documentation at Netfilter/iptables documentation.

Let's see a full-blown example. In this case, my firewall/gateway policy states:

  • Connections to the firewall are only allowed through SSH (port 22).
  • The local network should have access to HTTP, HTTPS, and SSH (DNS should also be allowed).
  • ICMP traffic can contain payload and should not be allowed. Of course, we have to allow some ICMP traffic.
  • Port scans should be detected and logged.
  • SYN attacks should be avoided.
  • All other traffic should be dropped and logged.
FILE /etc/init.d/firewall
#!/sbin/openrc-run

IPTABLES=/sbin/iptables

IPTABLESSAVE=/sbin/iptables-save

IPTABLESRESTORE=/sbin/iptables-restore

FIREWALL=/etc/firewall.rules

DNS1=212.242.40.3

DNS2=212.242.40.51

#inside

IIP=10.0.0.2

IINTERFACE=eth0

LOCAL_NETWORK=10.0.0.0/24

#outside

OIP=217.157.156.144

OINTERFACE=eth1



opts="${opts} showstatus panic save restore showoptions rules"



depend() {

  need net

}



rules() {

  stop

  ebegin "Setting internal rules"



  einfo "Setting default rule to drop"

  $IPTABLES -P FORWARD DROP

  $IPTABLES -P INPUT   DROP

  $IPTABLES -P OUTPUT  DROP



  #default rule

  einfo "Creating states chain"

  $IPTABLES -N allowed-connection

  $IPTABLES -F allowed-connection

  $IPTABLES -A allowed-connection -m state --state ESTABLISHED,RELATED -j ACCEPT

  $IPTABLES -A allowed-connection -i $IINTERFACE -m limit -j LOG --log-prefix \

      "Bad packet from ${IINTERFACE}:"

  $IPTABLES -A allowed-connection -j DROP



  #ICMP traffic

  einfo "Creating icmp chain"

  $IPTABLES -N icmp_allowed

  $IPTABLES -F icmp_allowed

  $IPTABLES -A icmp_allowed -m state --state NEW -p icmp --icmp-type \

      time-exceeded -j ACCEPT

  $IPTABLES -A icmp_allowed -m state --state NEW -p icmp --icmp-type \

      destination-unreachable -j ACCEPT

  $IPTABLES -A icmp_allowed -p icmp -j LOG --log-prefix "Bad ICMP traffic:"

  $IPTABLES -A icmp_allowed -p icmp -j DROP



  #Incoming traffic

  einfo "Creating incoming ssh traffic chain"

  $IPTABLES -N allow-ssh-traffic-in

  $IPTABLES -F allow-ssh-traffic-in

  #Flood protection

  $IPTABLES -A allow-ssh-traffic-in -m limit --limit 1/second -p tcp --tcp-flags \

      ALL RST --dport ssh -j ACCEPT

  $IPTABLES -A allow-ssh-traffic-in -m limit --limit 1/second -p tcp --tcp-flags \

      ALL FIN --dport ssh -j ACCEPT

  $IPTABLES -A allow-ssh-traffic-in -m limit --limit 1/second -p tcp --tcp-flags \

      ALL SYN --dport ssh -j ACCEPT

  $IPTABLES -A allow-ssh-traffic-in -m state --state RELATED,ESTABLISHED -p tcp --dport ssh -j ACCEPT



  #outgoing traffic

  einfo "Creating outgoing ssh traffic chain"

  $IPTABLES -N allow-ssh-traffic-out

  $IPTABLES -F allow-ssh-traffic-out

  $IPTABLES -A allow-ssh-traffic-out -p tcp --dport ssh -j ACCEPT



  einfo "Creating outgoing dns traffic chain"

  $IPTABLES -N allow-dns-traffic-out

  $IPTABLES -F allow-dns-traffic-out

  $IPTABLES -A allow-dns-traffic-out -p udp -d $DNS1 --dport domain \

      -j ACCEPT

  $IPTABLES -A allow-dns-traffic-out -p udp -d $DNS2 --dport domain \

     -j ACCEPT



  einfo "Creating outgoing http/https traffic chain"

  $IPTABLES -N allow-www-traffic-out

  $IPTABLES -F allow-www-traffic-out

  $IPTABLES -A allow-www-traffic-out -p tcp --dport www -j ACCEPT

  $IPTABLES -A allow-www-traffic-out -p tcp --dport https -j ACCEPT



  #Catch portscanners

  einfo "Creating portscan detection chain"

  $IPTABLES -N check-flags

  $IPTABLES -F check-flags

  $IPTABLES -A check-flags -p tcp --tcp-flags ALL FIN,URG,PSH -m limit \

      --limit 5/minute -j LOG --log-level alert --log-prefix "NMAP-XMAS:"

  $IPTABLES -A check-flags -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP

  $IPTABLES -A check-flags -p tcp --tcp-flags ALL ALL -m limit --limit \

      5/minute -j LOG --log-level 1 --log-prefix "XMAS:"

  $IPTABLES -A check-flags -p tcp --tcp-flags ALL ALL -j DROP

  $IPTABLES -A check-flags -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG \

      -m limit --limit 5/minute -j LOG --log-level 1 --log-prefix "XMAS-PSH:"

  $IPTABLES -A check-flags -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP

  $IPTABLES -A check-flags -p tcp --tcp-flags ALL NONE -m limit \

      --limit 5/minute -j LOG --log-level 1 --log-prefix "NULL_SCAN:"

  $IPTABLES -A check-flags -p tcp --tcp-flags ALL NONE -j DROP

  $IPTABLES -A check-flags -p tcp --tcp-flags SYN,RST SYN,RST -m limit \

      --limit 5/minute -j LOG --log-level 5 --log-prefix "SYN/RST:"

  $IPTABLES -A check-flags -p tcp --tcp-flags SYN,RST SYN,RST -j DROP

  $IPTABLES -A check-flags -p tcp --tcp-flags SYN,FIN SYN,FIN -m limit \

      --limit 5/minute -j LOG --log-level 5 --log-prefix "SYN/FIN:"

  $IPTABLES -A check-flags -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP



  # Apply and add invalid states to the chains

  einfo "Applying chains to INPUT"

  $IPTABLES -A INPUT -m state --state INVALID -j DROP

  $IPTABLES -A INPUT -p icmp -j icmp_allowed

  $IPTABLES -A INPUT -j check-flags

  $IPTABLES -A INPUT -i lo -j ACCEPT

  $IPTABLES -A INPUT -j allow-ssh-traffic-in

  $IPTABLES -A INPUT -j allowed-connection



  einfo "Applying chains to FORWARD"

  $IPTABLES -A FORWARD -m state --state INVALID -j DROP

  $IPTABLES -A FORWARD -p icmp -j icmp_allowed

  $IPTABLES -A FORWARD -j check-flags

  $IPTABLES -A FORWARD -o lo -j ACCEPT

  $IPTABLES -A FORWARD -j allow-ssh-traffic-in

  $IPTABLES -A FORWARD -j allow-www-traffic-out

  $IPTABLES -A FORWARD -j allowed-connection



  einfo "Applying chains to OUTPUT"

  $IPTABLES -A OUTPUT -m state --state INVALID -j DROP

  $IPTABLES -A OUTPUT -p icmp -j icmp_allowed

  $IPTABLES -A OUTPUT -j check-flags

  $IPTABLES -A OUTPUT -o lo -j ACCEPT

  $IPTABLES -A OUTPUT -j allow-ssh-traffic-out

  $IPTABLES -A OUTPUT -j allow-dns-traffic-out

  $IPTABLES -A OUTPUT -j allow-www-traffic-out

  $IPTABLES -A OUTPUT -j allowed-connection



  #Allow client to route through via NAT (Network Address Translation)

  $IPTABLES -t nat -A POSTROUTING -o $OINTERFACE -j MASQUERADE

  eend $?

}



start() {

  ebegin "Starting firewall"

  if [ -e "${FIREWALL}" ]; then

    restore

  else

    einfo "${FIREWALL} does not exists. Using default rules."

    rules

  fi

  eend $?

}



stop() {

  ebegin "Stopping firewall"

  $IPTABLES -F

  $IPTABLES -t nat -F

  $IPTABLES -X

  $IPTABLES -P FORWARD ACCEPT

  $IPTABLES -P INPUT   ACCEPT

  $IPTABLES -P OUTPUT  ACCEPT

  eend $?

}



showstatus() {

  ebegin "Status"

  $IPTABLES -L -n -v --line-numbers

  einfo "NAT status"

  $IPTABLES -L -n -v --line-numbers -t nat

  eend $?

}



panic() {

  ebegin "Setting panic rules"

  $IPTABLES -F

  $IPTABLES -X

  $IPTABLES -t nat -F

  $IPTABLES -P FORWARD DROP

  $IPTABLES -P INPUT   DROP

  $IPTABLES -P OUTPUT  DROP

  $IPTABLES -A INPUT -i lo -j ACCEPT

  $IPTABLES -A OUTPUT -o lo -j ACCEPT

  eend $?

}



save() {

  ebegin "Saving Firewall rules"

  $IPTABLESSAVE > $FIREWALL

  eend $?

}



restore() {

  ebegin "Restoring Firewall rules"

  $IPTABLESRESTORE < $FIREWALL

  eend $?

}



restart() {

  svc_stop; svc_start

}



showoptions() {

  echo "Usage: $0 {start|save|restore|panic|stop|restart|showstatus}"

  echo "start)      will restore setting if exists else force rules"

  echo "stop)       delete all rules and set all to accept"

  echo "rules)      force settings of new rules"

  echo "save)       will store settings in ${FIREWALL}"

  echo "restore)    will restore settings from ${FIREWALL}"

  echo "showstatus) Shows the status"

}

Some advice when creating a firewall:

  1. Create your firewall policy before implementing it.
  2. Keep it simple.
  3. Know how each protocol works (read the relevant RFC (request For comments))
  4. Keep in mind that a firewall is just another piece of software running as root.
  5. Test your firewall.

If you think that iptables is hard to understand or takes too long to set up a decent firewall you could use Shorewall. It basically uses iptables to generate firewall rules, but concentrates on rules and not specific protocols.