Here's a way to stop the same individuals from trying to penetrate a system's defenses from
the same IP address, regardless of whether or not the system is rebooted. It adds the attacker's
IP address to a file that is used as a table by PF. It's loaded each time the system is started.
None of the addresses are lost. You don't have to start all over going through the process of
disallowing someone trying to get in who has tried before from an address already in the file.
Here's how to do it.
First, create a file naming it whatever you want and placing it wherever you want, just so long
as it's accessible by PF. For example's sake, let's call the file attackers and place it in /etc.
Then, do chmod 0600 /etc/attackers. After that, create a script to do the log checking and
concatenate any new addresses into attackers. Let's call it tabletest.sh and put it in /root/bin,
do chmod 700 /root/bin/tabletest.sh which makes the file executable, writable and readable
by the ownwer only. Now put the following in the script:
#!/bin/sh
#
# Query pf for new bruteforce table entries and, if any are found,
# compare them to historical record in /etc/attackers. If they are
# in fact new, concatenate them onto the attackers file, then flush
# the bruteforce table. Actually the last part, the flush, is not
# really necessary. That's up to the operator. I just thought it'd
# make the routine a bit less error prone. The whole point of the
# exercise was to get better at scripting and to keep the record
# of previous attacks so they wouldn't be lost, thus saving pf a
# little work the next time an attack comes from one of the previous
# IP addresses.
# Query the bruteforce table and output to the file bruteforced
# in /root/bin.
#
pfctl -t bruteforce -T show > /root/bin/bruteforced
#
# grep bruteforced for '.' Easier than using numbers.
#
if grep . /root/bin/bruteforced;
#
#If any IP's in bruteforced the script continues. If not, jump to else.
#
then
#
# diff bruteforced against /etc/attackers for new IP addresses.
# grep for '<' and cut it which will be in front of the old addresses.
# Next, sed removes leading whitespaces, the output is concatenated
# onto /etc/attackers which is then sorted numerically. Then sed checks
# for and removes duplicate lines except for the first line, writing the
# output to /root/bin/newattackers which is in turn then copied to
# /etc/attackers to /etc/attackers, overwriting the old file. Then
# echo overwrites # the old file /root/bin/bruteforced with the right
# redirection symbol > effectively zeroing the file out.
# Now pfctl runs, flushing the table bruteforce which you'll have to
# create in your /etc/pf.conf along with an attackers table too.
# Then pfctl replaces the old attackers table with the contents of
# the updated file /etc/attackers.
# If nothing was found this go around, then else echoes a message
# there were no new entries during this last time period which is
# up to the operator and how far apart the cronjob spaces out the
# checks using tabletest.sh.
#
diff /root/bin/bruteforced /etc/attackers | grep \< | cut -c 2-22 | \
sed -e 's/^[ \t]*//' >> /etc/attackers && sort -n /etc/attackers | \
sed '$!N; /^\(.*\)\n\1$/!P; D' > /root/bin/newattackers && \
cp /root/bin/newattackers /etc/attackers && echo > /root/bin/bruteforced
pfctl -t bruteforce -T flush && pfctl -t attackers -T replace -f /etc/attackers
#
# If nothing was found in initial test, echo the message for root's mail.
#
else
echo No new entries in the bruteforce table this time period.
#
# At this point, the script finishes.
#
fi
In /etc/pf.conf you need the following tables:
# Tables
table <attackers> persist file "/etc/attackers"
table <bruteforce> persist
In your ruleset, you can use:
# Block attackers from continually updated list
# /etc/attackers
block in on $ext_if from <attackers> to any
# Block brute force attackers
block quick from <bruteforce>
Say for instance you're running ssh and pure-ftpd. You could put:
pass in quick proto tcp from any to $ext_if port { 21,22,>49199 } \
flags S/SA keep state \
(max-src-conn 5, max-src-conn-rate 5/5, \
overload <bruteforce> flush global)
If you were just worried about ssh, you could have:
pass in quick proto tcp from any to $ext_if port 22 \
flags S/SA keep state \
(max-src-conn 5, max-src-conn-rate 5/5, \
overload <bruteforce> flush global)
With all that in place, to get started, go ahead and create the file bruteforced
in /root/bin. Set tabletest.sh to run in a cronjob. Example:
35 * * * * /root/bin/tabletest.sh
I realize there are probably other ways to do this. This was simply an exercise in
learning more about scripting and manipulating pf tables. Any feed back on
it, as always, greatly appreciated.
Cheers!
No affiliation between this site and the OpenBSD project exists or is implied.