Using Dynamic Blocklists with IPtables + IPset
In this tutorial, we will learn how to make automatically updated block lists of known bad addresses using ipset and iptables.
This provides a proactive security approach that can use external datasets to discover addresses known for malicious activity and prevent them from accessing your web server.
A quick overview on IPSet
In some of my earlier tutorials, we learned how we could use ipset to create dynamic filtering rules which are independent of iptables.
For example, if we wanted to give our friend temporary access to SSH,
# create ipset
sudo ipset create ssh_ip hash:ip
# add friends ip
sudo ipset add 192.0.2.62 ssh_ip
# remove his ip
sudo ipset del 192.0.2.62
Then in IPTables, we have the following rule
-A INPUT -m state --state NEW -p tcp --dport 22 \
-m set --match-set ssh_ip src -j ACCEPT
Let's apply this concept to make powerful, automatically updated rule sets.
We pick a blockset from https://github.com/firehol/blocklist-ipsets
These block lists are based on various sensor nodes that monitor what hosts have attempted to intrude into them, and by aggregating them, we get a good idea of which addresses are continual sources of trouble. These lists are ephemeral, so a cronjob is required to keep them up to date.
First, we install the ipset and iprange utilities
$ sudo apt install ipset iprange
Next, we create our first block list
$ sudo ipset create firehol_webserver hash:net
Now we populate it (this may take a while)
# this must be run as root
curl https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_webserver.netset | iprange | while read line; do ipset add firehol_webserver $line; done
Now we create our iptables rules.
We need to make sure the rule to block bad addresses is before the rule to allow http/https, otherwise it will be of no effect.
We get a numbered list of our iptables rules and take note of where we allow 80,443
$ sudo iptables -L -n --line-numbers
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 DROP all -- 0.0.0.0/0 0.0.0.0/0 state INVALID
2 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
3 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
4 ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 state NEW icmptype 8 limit: avg 1/sec burst 5
5 ACCEPT tcp -- 0.0.0.0/0 10.88.8.8 state NEW tcp flags:0x17/0x02 multiport dports 80,443
6 REJECT tcp -- 0.0.0.0/0 10.88.8.8 state NEW tcp flags:0x17/0x02 reject-with tcp-reset
In this case, we can see that we need to inset our rule at line 5, so that it immediately precedes the rule where 80,443 is accepted.
$ sudo iptables -I INPUT 5 -m state --state NEW -p tcp -m set --match-set firehol_webserver src -j REJECT --reject-with tcp-reset
Creating the cronscript to automatically update blocklist
Open your favourite text editor and paste the following into /etc/cron.daily/ipset
(make sure to replace URL and BLOCKLIST with your desired chocies)
#! /bin/bash
URL="https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_webserver.netset"
BLOCKLIST=firehol_webserver
curl $URL | iprange | while read line; do ipset add $BLOCKLIST $line; done
Then make sure the file is executable by running chmod +x /etc/cron.daily/ipset
.
Finally, we test the cron script is working as intended by running it (as root)
root@localhost:/etc/cron.daily# ./ipset
ipset v6.29: Element cannot be added to the set: it's already added
ipset v6.29: Element cannot be added to the set: it's already added
ipset v6.29: Element cannot be added to the set: it's already added
[...]
We can monitor the hit counts of our new ruleset
$ sudo iptables -L -nv --line-numbers | grep firehol_webserver
5 0 0 REJECT tcp -- * * 0.0.0.0/0 0.0.0.0/0 state NEW match-set firehol_webserver src reject-with tcp-reset