Add a chain to UFW to block intrusion attempts

Block hosts that are brute-forcing sshd by grepping and counting IP addresses for authentication failures in /var/log/auth.log. This was written for Debian, and is intended to co-exist with UFW rules.

A better solution is fail2ban, there may be others. However this is simple and light, and good enough to reduce logfile noise and to hinder the bots.

Add a new chain right at the start of the INPUT chain. This will hold our temporary block rules.

iptables -N blocked-ips
iptables -I INPUT 1 -j blocked-ips

Extract IP addresses of intrusion attempts by grepping /var/log/auth.log for authentication failures and filtering on the number of failed attempts.

grep 'authentication failure' /var/log/auth.log|egrep -o 'rhost=[[:digit:]]+.[[:digit:]]+.[[:digit:]]+.[[:digit:]]+'|gawk -f count.awk >blocked_ips.txt

The awk script used above, count.awk, is shown below.

BEGIN { FS="=" }
/rhost/ {
  ips[$2] = ips[$2] + 1;
}
END {
  for (key in ips)
    if (ips[key] > 5)
      print key;
}

Now block the hosts in blocked_ips.txt using iptables. There are more efficient ways to do this (iptables-restore), but simply iterating is fast enough (~2ms per IP on average).

for ip in $(cat blocked_ips.txt); do iptables -A blocked-ips -s $ip -j DROP; done

Remember to flush the chain prior to re-running.

iptables -F blocked-ips

A bash script to put it all together.

The following script can be used in a cron to block everything in the current auth.log file. I called it block_ips.sh. Remember to make it executable.

#!/bin/bash

# Define a file to hold IPs, a regex to match IP addresses, and
# the chain to hold the IP block rules.
TEMP_FILE='/run/blocked_ips.txt'
IP_REGEX='[[:digit:]]+.[[:digit:]]+.[[:digit:]]+.[[:digit:]]+'
CHAIN='blocked-ips'

# Ensure we have a chain for blocked IPs, and that it's checked first
iptables -L $CHAIN -n >/dev/null 2>&1 || \
        ( iptables -N $CHAIN && iptables -I INPUT 1 -j $CHAIN )

# Extract the auth failure IPs and pipe them through an awk
# script to count the occurrences of each.
grep 'authentication failure' /var/log/auth.log | \
egrep -o "rhost=$IP_REGEX" | \
gawk '
 BEGIN { FS="=" }
 /rhost/ {
   ips[$2] = ips[$2] + 1;
 }
 END {
   for (key in ips)
     if (ips[key] >= 5)
       print key
}' >$TEMP_FILE

# Flush the chain and add rules for current IPs to block.
iptables -F $CHAIN

for ip in $(cat $TEMP_FILE); do iptables -A $CHAIN -s $ip -j DROP; done

There are a lot of ways to improve on this, for example including expiry times as comments, and auto-expiring.