IPtables: Constructing user chains

Now that we are familiar with how iptables is organized we can start defining our own chains. This allows for greater control, flexibility, and in some cases, better efficiency. The most profound efficiency gain is not in the processing by the system, it's in the maintenance, upkeep, and auditing by the sysadmin. We'll begin with a simple "LOG 'N DROP" that we will use in place of -j DROP so we can keep tabs of what the system is filtering

Creating a logging chain

Next we will create a chain called `LOGDROP` This chain logs packets passing through it at a rate of 10/min, then drops them. We will also create a `LOGACCEPT` chain which can be used in cases where you want to log accepted traffic.

First we create the chains with the command

 $ sudo iptables -N LOGDROP
 $ sudo iptables -N LOGACCEPT

Now we give them some rules:

$ sudo iptables --append LOGDROP    --match limit --limit 1/min 
                 --limit-burst 10    --jump LOG    --log-prefix       
                 "[iptables-DROP]:" --log-level 4
$ sudo iptables --jump DROP
# repeat for log accept

Now, we run iptables-save and then replace -j DROP with -j LOGDROP.

You can do this using find and replace in your favorite text editor. We can use sed to generate new rule file from the command line

$ iptables-save | 
      sed s/'-j DROP'/'-j LOGDROP'/ |
      sed s/'-A LOGDROP -j LOGDROP'/'-A LOGDROP -j DROP'/ |
   tee iptables-$(date +%d-%m-%y-%H:%M:%S:%Z)

The first sed command substitutes -j DROP with -j LOGDROP. This will be fine except for the LOGDROP chain, so we use the second sed command to fix that.

Reading the Log
There are two ways of reading the firewall log. With rsyslogd running, you can read /var/log/kern.log, or alternatively you can watch it in a real time with dmesg -k --follow. The output looks like this:

[11022.129321] [iptables-DROP]: IN=enp0s3 OUT= MAC=08:00:27:2c:cf:c8:00:11:0a:00:55:88:09:00 SRC=10.10.0.29 DST=10.100.254.1 LEN=44 TOS=0x00 PREC=0x00 TTL=44 ID=10012 PROTO=TCP SPT=44380 DPT=53 WINDOW=1024 RES=0x00 SYN URGP=0 

Creating chains to organize our firewall

We will use custom chains to organize and simplify the following example firewall

-P INPUT DROP
-P FORWARD DROP
-P OUTPUT ACCEPT
-N LOGACCEPT
-N LOGDROP

# Some essential 'boilerplate' rules
-A INPUT -m state --state INVALID -j LOGDROP
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT

# Allow Some ICMP (ICMP)
-A INPUT -p icmp -m icmp --icmp-type 8 -m limit --limit 1/sec --limit-burst 4 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 12 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 3 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 11 -j ACCEPT

# Prevent Malicious Traffic (INVALIDTCP)
-A INPUT -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m state --state NEW -j LOGDROP
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j LOGDROP
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m limit --limit 1/sec --limit-burst 3

# Allow A local subnet access to ssh and dns (LOCALNET)
-A INPUT -s 10.73.1.0/24 -p tcp -m tcp --dport 22 -j LOGACCEPT
-A INPUT -s 10.73.1.0/24 -p tcp -m tcp --dport 53 -j LOGACCEPT
-A INPUT -s 10.73.1.0/24 -p udp -m udp --dport 53 -j ACCEPT
	
# Allow Access to SSH,FTP (ADMINSET)
-A INPUT -m set --match-set ADMIN_ADDR src -p tcp -m tcp --dport 22 -j LOGACCEPT
-A INPUT -m set --match-set ADMIN_ADDR src -p tcp -m tcp --dport 80 -j LOGACCEPT
-A INPUT -m set --match-set ADMIN_ADDR src -p tcp -m tcp --dport 587 -j LOGACCEPT

# Finally log all 
-A INPUT -j LOGDROP
	
-A OUTPUT -m state --state INVALID -j LOGDROP
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

-A LOGACCEPT -m limit --limit 1/min --limit-burst 10 -j LOG --log-prefix "ipt-accept: "
-A LOGACCEPT -j ACCEPT

-A LOGDROP -m limit --limit 1/min --limit-burst 10 -j LOG --log-prefix "ipt-drop: "
-A LOGDROP -j DROP

We will create four new chains: ICMP INVALIDTCP LOCALNET ADMINSET
Before making big changes to the firewall, backup the current iptables rules. If on a remote machine, make sure you have some form of emergency console access available if you need it.

Defining the chains

sudo iptables-save | tee /root/iptables-backup-$(date +%d-%m-%y-%H:%M:%S:%Z)
sudo iptables -t filter -N ICMP
sudo iptables -t mangle -N INVALIDTCP
sudo iptables -t filter -N LOCALNET
sudo iptables -t filter -N ADMINSET

First let's populate ICMP chain

iptables -A ICMP -p icmp -m icmp --icmp-type destination-unreachable -j ACCEPT
iptables -A ICMP -p icmp -m icmp --icmp-type parameter-problem       -j ACCEPT
iptables -A ICMP -p icmp -m icmp --icmp-type time-exceeded           -j ACCEPT
iptables -A ICMP -p icmp -m icmp --icmp-type echo-request \
                         -m limit --limit 1/sec --limit-burst 4      -j ACCEPT
iptables -A ICMP -j DROP

Next we do INVALIDTCP

Note this chain is a little bit different to the others, as we will block these invalid packets in the `MANGLE` table rather than `FILTER`. This increases the speed at which these packets can be dropped, providing improved protection against denial of service attacks
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags FIN,SYN FIN,SYN -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags FIN,RST FIN,RST -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags FIN,ACK FIN     -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags ACK,URG URG     -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags ACK,FIN FIN     -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags ACK,PSH PSH     -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags ALL ALL         -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags ALL NONE        -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags ALL FIN,PSH,URG -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j DROP
iptables  -t mangle -A INVALIDTCP -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP
iptables  -t mangle -A INVALIDTCP -j RETURN

Then we add it to the MANGLE table

iptables -t mangle -A PREROUTING -j INVALIDTCP

Then ADMINSET and LOCALNET

iptables -A LOCALNET ! -s 10.73.0.0/24 -j RETURN iptables -A LOCALNET -p udp -m udp --dport 53 -j ACCEPT iptables -A LOCALNET -p tcp -m tcp --dport 53 --syn -j ACCEPT iptables -A LOCALNET -p tcp -m tcp --dport 22 --syn -j ACCEPT iptables -A LOCALNET -j LOGDROP
 iptables -A ADMINSET -p tcp -m tcp --dport 22 --syn -j ACCEPT
 iptables -A ADMINSET -p tcp -m tcp --dport 80 --syn -j ACCEPT
 iptables -A ADMINSET -p tcp -m tcp --dport 587 --syn -j ACCEPT
 iptables -A ADMINSET -p tcp -m tcp --dport 443 --syn -j ACCEPT
 iptables -A ADMINSET -j RETURN

Putting it all together

At this point, we're going to have to edit our INPUT chain. Do an `iptables-save` and edit the saved file.

Now our input chain has been shrunk to this:

-A INPUT -m state --state INVALID -j LOGDROP
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -j ICMP
-A INPUT -s 10.73.0.0/22 -j LOCALNET
-A INPUT -p tcp -m set --match-set ADMIN_ADDR src -j ADMINSET
-A INPUT -j LOGDROP  

Now here is the complete, final, iptables-save output

(Note aug 17 2017: I refactored some of the examples)

# Please check out https://etherarp.net
# Written by Rohan. 
# Generated by iptables-save v1.6.0 on Mon Feb 13 23:26:32 2017
*mangle
:PREROUTING ACCEPT [128:8788]
:INPUT ACCEPT [128:8788]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [93:7124]
:POSTROUTING ACCEPT [93:7124]
:INVALIDTCP - [0:0]
-A PREROUTING -s 192.168.88.1/32 -d 255.255.255.255/32 -j DROP
-A PREROUTING -j INVALIDTCP
-A INVALIDTCP -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags FIN,RST FIN,RST -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags ACK,URG URG -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags PSH,ACK PSH -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,PSH,ACK,URG -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,PSH,URG -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,PSH,URG -j DROP
-A INVALIDTCP -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,ACK,URG -j DROP
-A INVALIDTCP -j RETURN
COMMIT
# Completed on Mon Feb 13 23:26:32 2017
# Generated by iptables-save v1.6.0 on Mon Feb 13 23:26:32 2017
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [14:840]
:ADMINSET - [0:0]
:ICMP - [0:0]
:LOCALNET - [0:0]
:LOGACCEPT - [0:0]
:LOGDROP - [0:0]
-A INPUT -m state --state INVALID -j LOGDROP
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -j ICMP
-A INPUT -s 10.73.0.0/22 -j LOCALNET
-A INPUT -p tcp -m set --match-set ADMIN_ADDR src -j ADMINSET
-A INPUT -j LOGDROP
-A OUTPUT -m state --state INVALID -j LOGDROP
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -m state --state INVALID -j LOGDROP
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A ADMINSET -p tcp -m tcp --dport 22 --tcp-flags FIN,SYN,RST,ACK SYN -j ACCEPT
-A ADMINSET -p tcp -m tcp --dport 80 --tcp-flags FIN,SYN,RST,ACK SYN -j ACCEPT
-A ADMINSET -p tcp -m tcp --dport 587 --tcp-flags FIN,SYN,RST,ACK SYN -j ACCEPT
-A ADMINSET -p tcp -m tcp --dport 443 --tcp-flags FIN,SYN,RST,ACK SYN -j ACCEPT
-A ADMINSET -j RETURN
-A ICMP -p icmp -m icmp --icmp-type 12 -j ACCEPT
-A ICMP -p icmp -m icmp --icmp-type 11 -j ACCEPT
-A ICMP -p icmp -m icmp --icmp-type 3 -j ACCEPT
-A ICMP -p icmp -m icmp --icmp-type 8 -m limit --limit 1/sec --limit-burst 4 -j ACCEPT
-A ICMP -p icmp -m icmp --icmp-type 0 -j ACCEPT
-A ICMP -j DROP
-A LOCALNET ! -s 10.73.0.0/22 -j RETURN
-A LOCALNET -p udp -m udp --dport 53 -j ACCEPT
-A LOCALNET -p tcp -m tcp --dport 53 --tcp-flags FIN,SYN,RST,ACK SYN -j ACCEPT
-A LOCALNET -p tcp -m tcp --dport 22 --tcp-flags FIN,SYN,RST,ACK SYN -j ACCEPT
-A LOCALNET -j LOGDROP
-A LOGACCEPT -m limit --limit 1/min --limit-burst 10 -j LOG --log-prefix "ipt-accept: "
-A LOGACCEPT -j ACCEPT
-A LOGDROP -m limit --limit 1/min --limit-burst 10 -j LOG --log-prefix "ipt-deny: "
-A LOGDROP -j DROP
COMMIT
# Completed on Mon Feb 13 23:26:32 2017