Country Level Blocking Using Ipset & Iptables

The Problem

Your server is continually receiving unwanted traffic from problematic global locations. This traffic is usually generated by scripts – for example, here’s an excerpt from /var/log/auth.log where we can see six attempted ssh logins in two seconds for the root user from the same IP address:

Jun 27 13:50:38 lisa sshd[17608]: Failed password for root from 131.255.133.60 port 4790 ssh2
Jun 27 13:50:39 lisa sshd[17608]: Failed password for root from 131.255.133.60 port 4790 ssh2
Jun 27 13:50:39 lisa sshd[17608]: Failed password for root from 131.255.133.60 port 4790 ssh2
Jun 27 13:50:39 lisa sshd[17608]: Failed password for root from 131.255.133.60 port 4790 ssh2
Jun 27 13:50:39 lisa sshd[17608]: Failed password for root from 131.255.133.60 port 4790 ssh2
Jun 27 13:50:40 lisa sshd[17608]: Failed password for root from 131.255.133.60 port 4790 ssh2

And here’s an excerpt from Apache logs showing failed requests for wp-login.php:

31.182.211.252 - - [27/Jun/2017:09:24:43 +0100] "GET /wp-login.php HTTP/1.1" 404 22269 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"
117.199.19.97 - - [27/Jun/2017:13:50:24 +0100] "GET /wp-login.php HTTP/1.1" 404 22264 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"
203.7.58.252 - - [27/Jun/2017:15:11:30 +0100] "GET /wp-login.php HTTP/1.1" 404 22802 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"
171.79.85.240 - - [27/Jun/2017:15:28:12 +0100] "GET /wp-login.php HTTP/1.1" 404 22269 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"

Side note – If you have a WordPress install I highly recommend you change the URL of the login page, this can be done using a plugin such as WPS Hide Login.

The Solution

Use ipset to create a hash:net of country blocks, and then add them into the top of the input chain of iptables. From the below script, and assuming that the name of the hash:net is blocklist, this looks like:

/sbin/iptables -I INPUT -m set --match-set blocklist src -j DROP

That’s it, all of the IP ranges in blocklist are now dropped at the firewall. Of course, to populate and update blocklist by hand would be time consuming, so this is where we use the help of the excellent guys at IPdeny who provide text files of country IP blocks.

The Script

This script is a result of the various ideas I’ve found on the internet to drop unwanted traffic at the firewall. Countries blocked in this script are China, Russia, Ukraine and Korea but you can easily add to them – I’ve also added some extra ranges that don’t appear in the zone files.

Note that I’ve double flushed iptables – without it ipset destroy will return an error if you run it more than once because the set blocklist will already be loaded into the kernel; this makes the script flexible so that you can run it whenever you want to pick up changes in the zone files from Ipdeny. I run this script daily via a cronjob but also set it to run at @reboot – hence export TERM=${TERM:-dumb} to suppress error messages about term not set. Copy / paste and then chmod 755 to run (as root).

#!/bin/sh
export TERM=${TERM:-dumb}
clear
temp=/root/ipblocklist

if [ ! -d $temp ]
then mkdir $temp
else rm -rf $temp/* >/dev/null
fi

cd $temp

banned="cn ru ua kr"

for i in $banned; do
wget http://www.ipdeny.com/ipblocks/data/aggregated/$i-aggregated.zone
cat $i-aggregated.zone > $i.txt
cat $i.txt >> blocklist.txt
done

/sbin/iptables -F
/sbin/iptables -P INPUT ACCEPT
/sbin/iptables -P FORWARD ACCEPT
/sbin/iptables -P OUTPUT ACCEPT
/sbin/iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
/usr/sbin/ipset destroy
/usr/sbin/ipset create blocklist hash:net
/usr/sbin/ipset --add blocklist 46.119.0.0/16
/usr/sbin/ipset --add blocklist 211.63.0.0/16
/usr/sbin/ipset --add blocklist 175.215.0.0/16
/usr/sbin/ipset --add blocklist 121.182.0.0/16

for i in `cat blocklist.txt`; do
/usr/sbin/ipset --add blocklist $i
done

/sbin/iptables -F
/sbin/iptables -P INPUT ACCEPT
/sbin/iptables -P FORWARD ACCEPT
/sbin/iptables -P OUTPUT ACCEPT
/sbin/iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
/sbin/iptables -I INPUT -m set --match-set blocklist src -j DROP
/sbin/iptables -A INPUT -i lo -j ACCEPT
/sbin/iptables -A OUTPUT -o lo -j ACCEPT
/sbin/iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT
/sbin/iptables -A INPUT -p tcp --dport ssh -j ACCEPT
/sbin/iptables -A INPUT -p tcp --dport 25 -j ACCEPT
/sbin/iptables -A INPUT -p tcp --dport 995 -j ACCEPT
/sbin/iptables -A INPUT -p tcp --dport 143 -j ACCEPT
/sbin/iptables -A INPUT -p tcp --dport 993 -j ACCEPT
/sbin/iptables -A INPUT -p tcp --dport 443 -j ACCEPT
/sbin/iptables -A INPUT -p tcp --dport 80 -j ACCEPT
/sbin/iptables -P INPUT DROP
/sbin/iptables -P FORWARD DROP
/sbin/iptables -P OUTPUT ACCEPT

echo "`cat blocklist.txt | wc -l` ip blocks loaded"
rm -rf $temp > /dev/null