October 4, 2014
Receive a report about (banned) IP using newbrute
When you manage servers that are accessible from the web, you have to deal with bots (unfortunately). One of my OpenBSD server is regularly attacked, despite the strict and restrictive pf(4) config. I also tried to write a "paranoid" config for sshd(8). This combo seems to be kinda efficient for now.
Even if the dumb "dudes" are unable to log on, my logs are
"spammed" by those unsuccessful attempts. Most of the time (around
90%), the attacks came from China. Nothing impressive indeed,
because you probably knew that already. Sometimes, I needed to ban
an entire subnet to prevent the brute force shot every 10
minutes, as I did with 222.186.58.0/24
. The "smarter"
ones try to connect one time per 15 minutes, the others are stupid
enough to trigger the
PF's wrath,
in less than 10 seconds. Yep, you shouldn't mess with that
guy.
The special table which includes all those banned addresses, is very generous. Usually, two or three IPs are added everyday (some days are more "productive" though). I don't want to miss the additions (for administration & entertainment), so I wrote a tiny script designed to send me a mail, when one or more addresses join the table. It's like a report.
But why a mail? OpenBSD includes a useful feature that warns you, when important security changes occur on the system (new file, permissions) via security(8). This is great to "follow" the machine. I wanted to have something in that vein.
set -e
PF_TABLENAME="bruteforce"
PF_TABLEFILE="/etc/${PF_TABLENAME}"
trap 'print "An error occured. Exiting." && exit 1' ERR
trap 'clean_oldtable' EXIT
function clean_oldtable {
[[ -f /tmp/${PF_TABLENAME}_OLD ]] && rm "/tmp/${PF_TABLENAME}_OLD"
}
First of all, we use set -e
. When an error happens
($? != 0
), the script launches the ERR
trap (here a message) and closes. There is a function
named clean_oldtable
which is designed to clean an
existing file, in /tmp
. It's called just before the
script ends.
if [[ ! -r $PF_TABLEFILE ]]; then
print -u2 "No existing table file found"
exit 1
fi
clean_oldtable
cp "$PF_TABLEFILE" "/tmp/${PF_TABLENAME}_OLD"
pfctl -t "$PF_TABLENAME" -Tshow >"$PF_TABLEFILE"
One time per day, I save the black list to a file called
/etc/bruteforce
, using the command pfctl(8). I
decided to do that in case the system is halted or restarted, I
don't want to lose the "holy Grail". This file is required by
the script and therefore it won't run without it. The next lines
are obvious, the "previous" catalog is copied to /tmp
(I like to work inside that directory) and then, pfctl(8) output
is redirected to the specified file in /etc
, as I
said it earlier.
if ! diff -q "/tmp/${PF_TABLENAME}_OLD" "$PF_TABLEFILE" >/dev/null 2>&1; then
PF_TABLEDIFF="$(diff -u "/tmp/${PF_TABLENAME}_OLD" "$PF_TABLEFILE" \
| awk '/^\+/&&!/\+\+\+/{gsub(/\+/,"");print}')"
print "The following address(es) was/were added to the $PF_TABLENAME table:\n\n$PF_TABLEDIFF" \
| mail -s "New IP address(es) summary" root
else
print "No IP added in the $PF_TABLENAME table"
fi
Maybe you're a regular reader (it would mean we are three or
four)... If so, you know I like to "work" with awk(1) and
diff(1). The pure efficiency. I'm used to unified format, that's
why I chose -u
option. The awk(1) regex is quite
simple: we match the lines starting with +
symbol
and we do not "record" the +++ /etc/foo
summary,
from the header. +
needs to be escaped by
\
considering it refers to a metacharacter (from
what I know, OpenBSD has a nawk
variant).
gsub
is included to erase the leading "plus" sign.
I could achieve the same result with the lovely
while IFS= read
and save one process, but many
people would have probably warned me that I SHOULD try
awk(1). And I didn't want to introduce any error. Assuming the
diff(1) exit status is not equal to zero, a mail is sent. If not,
a message is displayed. It gives me visibility when I run it
manually. newbrute
is executed through
/etc/daily.local
.
The next day when you login, you will have to verify your email(s). An email has the following form:
Date: Fri, 3 Oct 2014 01:30:02 +0200 (CEST)
From: Charlie Root <root@foo.domain>
Message-Id: <201410022330.s92NU27k015450@foo.domain>
To: root@foo.domain
Subject: New IP address(es) summary
The following address(es) was/were added to the bruteforce table:
8.8.8.8
54.200.180.162
66.5.48.24
140.45.8.23
Oh by the way, I forgot to put the link. Perhaps you will read it...