IP Blocklisting
Install required software
sudo apt install ipset ipset-persistent
Configure blocklist
sudo mkdir -p /var/lib/blocklist
'spamhaus' DROP list
Do Not Route or Peer
curl https://www.spamhaus.org/drop/drop.txt | awk '/^[0-9]/{print $1}' | sudo tee /var/lib/blocklist/spamhaus.txt
Create ipset
sudo /usr/sbin/ipset create spamhaus hash:net hashsize 8192
Populate the ipset with the blocklist file:
#!/bin/bash
/usr/sbin/ipset flush spamhaus
while read ip; do
/usr/sbin/ipset add spamhaus $ip -exist || echo $ip
done < /var/lib/blocklist/spamhaus.txt
This will take about a minute to run.
abuse.ch c2 blocklist
Download the blocklist
curl https://feodotracker.abuse.ch/downloads/ipblocklist_recommended.txt | awk '/^[0-9]/{print $1}' | tr -d '\r' | sudo tee /var/lib/blocklist/abusech.txt
Create ipset
sudo /usr/sbin/ipset create abusech hash:ip hashsize 4096
Populate the ipset with the c2 file:
#!/bin/bash
/usr/sbin/ipset flush abusech
while read ip; do
/usr/sbin/ipset add abusech $ip -exist || echo $ip
done < /var/lib/blocklist/abusech.txt
Check the blocklist:
$ sudo /usr/sbin/ipset list -t
Name: spamhaus
Type: hash:net
Revision: 7
Header: family inet hashsize 8192 maxelem 65536 bucketsize 12 initval 0x68f90c54
Size in memory: 41520
References: 4
Number of entries: 900
Name: abusech
Type: hash:ip
Revision: 5
Header: family inet hashsize 4096 maxelem 65536 bucketsize 12 initval 0xe508fe5a
Size in memory: 9352
References: 0
Number of entries: 236
...
Check the blocklist by performing a lookup:
$ sudo /usr/sbin/ipset test spamhaus 168.151.54.25
Warning: 168.151.54.25 is in set spamhaus.
Iptables rules
Add an iptables rule to block ranges on the blocklist. For a server or workstation, this should probably go on the INPUT and OUTPUT chains:
-A INPUT -m set --match-set spamhaus src -j LOG --log-prefix "BLOCK:SPAMHAUS:" --log-level 4
-A INPUT -m set --match-set spamhaus src -j DROP -m comment --comment "drop droplisted IP ranges"
-A OUTPUT -m set --match-set spamhaus dst -j LOG --log-prefix "BLOCK:SPAMHAUS:" --log-level 4
-A OUTPUT -m set --match-set spamhaus dst -j DROP -m comment --comment "drop droplisted IP ranges"
-A OUTPUT -m set --match-set abusech dst -j LOG --log-prefix "BLOCK:ABUSECH:" --log-level 4
-A OUTPUT -m set --match-set abusech dst -j DROP -m comment --comment "drop droplisted IP ranges"
...
For a firewall, this should be added to FORWARD before any other rules that will take effect on the "outside" interface.
-A FORWARD -m set --match-set spamhaus src -j LOG --log-prefix "BLOCK:SPAMHAUS:" --log-level 4
-A FORWARD -m set --match-set spamhaus src -j DROP -m comment --comment "drop droplisted IP ranges"
-A FORWARD -m set --match-set spamhaus dst -j LOG --log-prefix "BLOCK:SPAMHAUS:" --log-level 4
-A FORWARD -m set --match-set spamhaus dst -j DROP -m comment --comment "drop droplisted IP ranges"
-A FORWARD -m set --match-set abusech dst -j LOG --log-prefix "BLOCK:ABUSECH:" --log-level 4
-A FORWARD -m set --match-set abusech dst -j DROP -m comment --comment "drop droplisted IP ranges"
Once set, the updated ruleset should be loaded:
sudo iptables-restore < /etc/iptables/rules.v4
Once added to the active rules, it should appear like this:
Chain OUTPUT (policy ACCEPT 65 packets, 8447 bytes)
pkts bytes target prot opt in out source destination
0 0 LOG all -- * * 0.0.0.0/0 0.0.0.0/0 match-set spamhaus dst LOG flags 0 level 4 prefix "BLOCK:SPAMHAUS:"
0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 match-set spamhaus dst /* drop droplisted IP ranges */
4 336 LOG all -- * * 0.0.0.0/0 0.0.0.0/0 match-set abusech dst LOG flags 0 level 4 prefix "BLOCK:ABUSECH:"
4 336 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 match-set abusech dst /* drop droplisted IP ranges */
Persistent ipset list
# ipset save > /etc/iptables/ipsets
Enable the boot-up service
sudo systemctl enable --now ipset.service
Update scripts
/opt/blocklist_spamhaus.sh
#!/bin/bash
if [ $(id -u) -ne 0 ]; then
echo "!! Must be root" && exit 1
fi
curl -s https://www.spamhaus.org/drop/drop.txt | awk '/^[0-9]/{print $1}' > /var/lib/blocklist/spamhaus.txt
/usr/sbin/ipset flush spamhaus
while read ip; do
/usr/sbin/ipset add spamhaus $ip -exist || echo $ip
done < /var/lib/blocklist/spamhaus.txt
/usr/sbin/ipset save > /etc/iptables/ipsets
/opt/blocklist_abusech.sh
#!/bin/bash
if [ $(id -u) -ne 0 ]; then
echo "!! Must be root" && exit 1
fi
curl -s https://feodotracker.abuse.ch/downloads/ipblocklist_recommended.txt | awk '/^[0-9]/{print $1}' | tr -d '\r' > /var/lib/blocklist/abusech.txt
/usr/sbin/ipset flush abusech
while read ip; do
/usr/sbin/ipset add abusech $ip -exist || echo $ip
done < /var/lib/blocklist/abusech.txt
/usr/sbin/ipset save > /etc/iptables/ipsets
/etc/cron.d/droplist_update
# Weekly update for spamhaus list
0 0 * * 1 root /opt/blocklist_spamhaus.sh
# Hourly update for abusech dynamic C2 list
0 * * * * root /opt/blocklist_abusech.sh
Nginx ban script
Create the initial ipset list:
ipset create scanners hash:ip hashsize 4096 counters
This script examines all available 'spam' logs for nginx, then groups by IP. If the IP has scanned the server 10 or more times, the IP is added to the ban list:
/opt/blocklist_nginx.sh
#!/bin/bash
# Ensure list exists
/usr/sbin/ipset list scanners 2>&1> /dev/null || \
/usr/sbin/ipset create scanners hash:ip hashsize 4096 counters
# IPs that hit the 'spam' log >10 times get banned:
for file in /var/log/nginx/spam.log*; do
if [[ "$file" != *"gz" ]]; then
awk '{print $1}' $file
fi
done | sort | uniq -c | \
awk '{if ($1 >= 10) print $2}' | \
xargs -n1 /usr/sbin/ipset add scanners -exist
# IPs that make requests containing "wget" also get banned:
grep -h "wget" /var/log/nginx/* | awk '{print $1}' | \
xargs -n1 /usr/sbin/ipset add scanners -exist
# Make banned IP list persistent
/usr/sbin/ipset save > /etc/iptables/ipsets
A second script runs once per week to flush the list & reset the counters. This will unban any IPs that have fallen below the threshold but were not removed by the main script:
/opt/blocklist_cleanup.sh
#!/bin/bash
/usr/sbin/ipset flush scanners
/opt/blocklist_nginx.sh
Both scripts are automated with cron
. The main script is run once per hour, and the cleanup script runs weekly:
/etc/cron.d/droplist_update
# Hourly update for nginx blocklist
00 * * * * root /opt/blocklist_nginx.sh
# Weekly cleanup for nginx blocklist
30 23 * * 7 root /usr/blocklist_clean.sh
Iptables rules to drop requests from these hosts:
-N LOG_DROP
-A LOG_DROP -j LOG --log-prefix "BLOCK:SCANNERS:" --log-level 4
-A LOG_DROP -j DROP
-A INPUT -m set --match-set scanners src -j LOG_DROP
Check counters on the ipset list:
ipset list scanners | tail -n +9 | sort -k 3 -g -r | awk '{print $1"\t"$3"\t"$5}' | less
SSH ban script
This script can use the same 'scanners' list as the nginx scanners script.
If the IP has attempted to connect with SSH >5 times in the last month, it will be added to the scanner ban list:
/opt/blocklist_sshd.sh
#!/bin/bash
journalctl -u ssh -S -4w | grep invalid | \
awk '{print $11}' | \
grep -E -o "([0-9]{1,3}[\.]){3}[0-9]{1,3}" | \
sort | uniq -c | sort -r | \
awk '{if ($1 > 5) print $2}' | \
xargs -n1 /usr/sbin/ipset add scanners -exist
# Make banned IP list persistent
/usr/sbin/ipset save > /etc/iptables/ipsets
Similar to the nginx script above, it should be regularly updated & flushed once per week.
/etc/cron.d/droplist_update
00 * * * * root /opt/blocklist_sshd.sh
30 23 * * 7 root /usr/blocklist_clean.sh