How To... | Why not..? | Scripts | Patches |
Last update: 2020-08-25
Are you annoyed of the bruteforce attacks against your SSH daemon? Are you looking for an easy way to block offending IPs on OpenBSD without installing an intrusion prevention system? In this post I present you a small shell script which uses tools from OpenBSDs base to achieve IP blocking. It has proven effective against slow bruteforce attacks.
The OpenBSD firewall pf(4) comes with the handy tool pfctl(8). We will use this to dynamically add offending IP addresses to pf. In pf.conf(5) you should have lines like these:
table <bruteforce> persist
block drop in quick on egress from <bruteforce> to any
Any packets from IP addresses in the table bruteforce
get silently
dropped on arrival by pf.
The script scans the file /var/log/authlog
for specific entries from
sshd(8). It uses sed(1)
to extract the IP address and store it to a file. As a safety mechanism
you can define addresses that should be excluded from blocking. The
remaining addresses are added to the pf table bruteforce
and all
existing connections from these IPs are dropped.
The script starts with the shebang and the optional header for rcs(1):
#!/bin/ksh
#
# $Header$
Next comes the array of IPs which should be excluded from blocking. This comes handy if you try to log in from a trusted system and fail, e. g. because you try to log in as root:
exclude[0]=192.0.2.19
exclude[1]=$(dig +short host.example.org)
dump=/tmp/ipdump.txt
list=/tmp/iplist.txt
The sed(1) script is embedded into the script. Each block scans for a
specific line in the log file /var/log/authlog
. sshd(8) frames the IP
address with the words from / with and port. For each line
found by sed(1) all text before and after the IP address is removed. The
address itself is then written to the file /tmp/ipdump.txt
:
sed -nE '/sshd.*Invalid user/{
s/.* from //
s/ port .*//w /tmp/ipdump.txt
}
/sshd.*Failed password for user root/{
s/.* from //
s/ port .*//w /tmp/ipdump.txt
}
/sshd.*Unable to negotiate with/{
s/.* with //
s/ port .*//w /tmp/ipdump.txt
}
/sshd.*Bad protocol version identification/{
s/.* from //
s/ port .*//w /tmp/ipdump.txt
}
/sshd.*Disconnected from authenticating user root/{
s/.* root //
s/ port .*//w /tmp/ipdump.txt
}' /var/log/authlog
The list in /tmp/ipdump.txt
is not ordered, contains duplicates and
maybe IPs from the exclusion array. The next step is to clean up the
list and store it to a new file:
cat $dump | sort -u > $list
rm -rf $dump
for ip in ${exclude[@]} ; do sed -Ei "/$ip/d" $list ; done
Now the IP address list is in a form which makes it suitable as input
for pfctl(8). First all addresses in the list are added to the table
bruteforce
. Second any existing states for IPs in the list get removed
from pf(4):
pfctl -q -f $list -t bruteforce -T add
for ip in $(cat $list) ; do pfctl -q -k $ip ; done
Finally, it is time for cleaning up:
rm -rf $list
exit 0
You can download the complete script file addbrute.sh.
Of course, you can run the script manually whenever you want to. But it
gets really useful if you let cron(8) do
the job for you. The script requires root
in order to run pfctl(8)
commands. So you can either use the global crontab(5)
file /etc/crontab
or the personal crontab(5) of root
. I prefer the
second method:
$ doas crontab -e
My entry in the crontab(5) of root
runs the script every 15 minutes:
*/15 * * * * /root/bin/addbrute.sh
The Internet is a dynamic place. And so your bruteforce
table should
be dynamic too. The IP address you block today might be assigned to you
tomorrow. Although it is not required for the script to work you may
want to clean old entries from bruteforce
regularly:
pfctl -t bruteforce -T expire 86400
With this command all IP addresses get removed from bruteforce
which
have not sent any packets to pf(4) for at least one day.
Where you place this command is a matter of taste. You can add it to the
script if you like. I prefer to run this command as part of the daily
maintenance of the OpenBSD system. So I add the above line to
/etc/daily.local
(see daily(8)).
The contents of any pf(4) tables are lost if you reboot the system. My post How to save and restore tables for pf(4) describes an easy solution for this.