Iptables brute force protection w/ nat

Setting up a vm on top of linux which communicates via a TAP adapter (on the 10.1.1.x network), I wanted iptables to prevent brute forcing to both the host ports (here 22 for ssh) and ports forwarded to the vm (here 443) as they are exposed to the internet. This little snippet does both by using iptables’ conntrack – simply more than 3 connections to either of those ports mentioned inside 60 seconds will lock that source IP out for 60 seconds.

The offending connections are marked in the nat table – prerouting chain, and checked (depending on whether forwarded or direct to host) in the filter table forward / input chains respectively. Logging is optional, you may choose to just DROP them once you’re confident on your ruleset.

Here’s a sample of the final ruleset I made:

*mangle
:PREROUTING ACCEPT [14138:1459925]
:INPUT ACCEPT [9963:618428]
:FORWARD ACCEPT [4175:841497]
:OUTPUT ACCEPT [8303:1284925]
:POSTROUTING ACCEPT [12453:2124922]
COMMIT
##
##
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [9184:1653115]
:BFLOG - [0:0]
:FWDLOG - [0:0]
:INPLOG - [0:0]
:OUTLOG - [0:0]
##
# input chain
##
-A INPUT -i lo -j ACCEPT
-A INPUT -m connmark --mark 0x5 -j BFLOG
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -j INPLOG
##
# forward chain
##
-A FORWARD -m connmark --mark 0x5 -j BFLOG
-A FORWARD -s 10.1.1.2/32 -j ACCEPT
-A FORWARD -d 10.1.1.2/32 -j ACCEPT
-A FORWARD -j FWDLOG
##
# new chain to log brute force attempts
##
-A BFLOG -j LOG --log-prefix "BFDROP:" --log-level 6
-A BFLOG -j DROP
##
# new chain to log forward attempts
##
-A FWDLOG -j LOG --log-prefix "FWDDROP:" --log-level 6
-A FWDLOG -j DROP
##
# log not accepted input attempts
##
-A INPLOG -j LOG --log-prefix "INPDROP:" --log-level 6
-A INPLOG -j DROP
COMMIT
##
##
*nat
:PREROUTING ACCEPT [2:128]
:INPUT ACCEPT [2:128]
:OUTPUT ACCEPT [37:2477]
:POSTROUTING ACCEPT [33:1984]
:ZNAT - [0:0]
##
# prerouting chain, nat table
##
-A PREROUTING -p tcp -m tcp --dport 443 -m conntrack --ctstate NEW -m recent --set --name VMPORT --mask 255.255.255.255 --rsource
-A PREROUTING -p tcp -m tcp --dport 443 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 --name VMPORT --mask 255.255.255.255 --rsource -j CONNMARK --set-xmark 0x5/0xffffffff
##
-A PREROUTING -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -m recent --set --name SSH --mask 255.255.255.255 --rsource
-A PREROUTING -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 --name SSH --mask 255.255.255.255 --rsource -j CONNMARK --set-xmark 0x5/0xffffffff
-A PREROUTING -j ZNAT
##
# postrouting for outbound nat/masquerade
##
-A POSTROUTING -o eth0 -j MASQUERADE
##
# table for inbound destination natting
##
-A ZNAT -p tcp -m tcp --dport 22 -j RETURN
-A ZNAT -p tcp -j DNAT --to-destination 10.1.1.2
COMMIT

For debugging, I cannot recommend highly enough using the TRACE target on the raw table (PREROUTING chain).

Something like:

iptables -I PREROUTING -t raw -p tcp --dport 22  -j TRACE

Will show in your log, every stop along the iptables chains for every packet, including which rule or policy was acted upon it to get it to it’s final destination and shape. Don’t forget to remove it when you’re done!

Also, install the actual conntrack utility to see the connection tracking tables.