Permanently Ban IP with Fail2Ban
If you’re not familiar with Fail2Ban: it’s a log analysis tool that detects failed login attempts for your SSH, FTP, etc services and uses IPTools firewall to temporarily drop connection requests from the offending IP addresses. Fail2Ban is intended to limit the effectiveness of brute force attacks. Very often an attack like this would originate from multiple IPs located on the same subnet. The Fail2Ban log entries may look something like this:
Jun 3 17:54:15 server01 fail2ban.actions: WARNING [ssh-iptables] Ban 116.10.191.184 Jun 3 18:04:16 server01 fail2ban.actions: WARNING [ssh-iptables] Unban 116.10.191.184 Jun 3 19:43:11 server01 fail2ban.actions: WARNING [ssh-iptables] Ban 116.10.191.196 Jun 3 19:47:57 server01 fail2ban.actions: WARNING [ssh-iptables] Ban 116.10.191.233 Jun 3 19:53:12 server01 fail2ban.actions: WARNING [ssh-iptables] Unban 116.10.191.196 Jun 3 19:57:58 server01 fail2ban.actions: WARNING [ssh-iptables] Unban 116.10.191.233 Jun 3 20:05:41 server01 fail2ban.actions: WARNING [ssh-iptables] Ban 116.10.191.199 Jun 3 20:15:41 server01 fail2ban.actions: WARNING [ssh-iptables] Unban 116.10.191.199
The suspicious subnet is “116.10.191”. However, Fail2Ban does not realize this, and only bans individual IPs for a limited amount of time (that can be configured in the Fail2Ban settings). This may allow a hacker with control over systems on multiple subnets to circumvent Fail2Ban. The idea behind the little script below is simple: once in a while check the log file where Fail2Ban is writing status (usually /var/log/messages); identify offending subnets; and add a permanent block for these subnets to the iptables firewall. It is possible you may end up blocking a legitimate user who had the misfortune of being on the same subnet as the hacker. However, this is not very likely.
One requirement (sort of) for this script to work is the “geoiplookup” command. You can install it on your CentOS/RHEL like so:
yum -y install GeoIP GeoIP-data
And here’s the script. You can add it to root crontab. Make sure it runs often enough so when the syslog rotates, you don’t lose too much time.
#!/bin/bash log="/var/log/messages" limit=5 zgrep "fail2ban.actions.*Ban" `echo "${log}*"` | grep -o '[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}' | awk -F'.' '{print $1"."$2"."$3}' | sort -u | while read line do count=$(zgrep -c "fail2ban.actions.*Ban.*${line}" `echo "${log}*"` | awk -F':' '{ sum+=$2} END {print sum}') if [ $${count} -ge ${limit} ] then if [ `/sbin/iptables -S | grep "${line}.0/24" | grep -c DROP` -eq 0 ] then /sbin/iptables -A INPUT -s ${line}.0/24 -j DROP log_entry="Permanently blocked subnet ${line}.0/24 from `geoiplookup ${line}.0 | awk -F',' '{print $NF}' | sed 's/^ //g'` after $${count} attacks" echo "${log_entry}" logger "${log_entry}" fi fi done /sbin/service iptables save
I know, you may be thinking “here’s a sysadmin with another script to save the world”. Hey, simple things still work in our complicated times.
Had to modify the script quite a bit to get it to work on my system, but nice approach! Was missing a couple $’s before the {count} and just removed everything after the pipe on the count = line as I’m not sure what it does.
Unfortunately, the “Crayon” plugin for WordPress has this annoying little bug, where “${c*}” loses the dollar sign. Probably some reserved internal variable.