Blacklistd: A new approach to blocking attackers
I think it's fair to say that if you run any internet-facing servers, they get attacked by all sorts of nasty things. Bruteforce attempts to login as root are far too common. Over the years there have been plenty of tools to address this issue - sshguard and fail2ban immediately come to mind.
These tools address the concern, but in a less than elegant way: parsing log files.

There has to be a better way!
What if we didn't have to scrape those log files, or write those complicated regular expressions at all? What if services like sshd instead notified our blacklist service when an invalid authorization event occurred? Enter blacklistd. Blacklistd serves as a middle-layer between your sensitive services (like sshd) and your firewall to provide configurable rules for filtering out attackers.
Let's get one thing straight: I'm no network engineering guru. I know enough to get around of course. I recently picked up a copy of The Book of PF to get more acquainted with the firewall side of things. Fortunately, to setup blacklistd you don't need to be a wizard. If you'd like an in-depth overview of how blacklistd functions, Christos Zoulas did an excellent talk a VBSDCon a few years back, that I've embedded below:
An in-depth talk about blacklistd by Christos Zoulas.
So what do we need to get started? As of this writing, blacklistd is available on NetBSD 7 and FreeBSD 11*. We'll be using FreeBSD 11 with the PF firewall for this tutorial.
* One caveat to using blacklistd on 11.0-RELEASE: There were a few bugs in the version that shipped. These are fixed in 11.1, and the maintainer has kindly provided a patched version for 11.0 at https://people.freebsd.org/~lidl/blacklistd.html.
First, make sure pf is configured.
Add pf_enable to your /etc/rc.conf (sysrc pf_enable="YES"
) and load the pf module (kldload pf
).
Next, we'll need to create a basic /etc/pf.conf. Make note of what your external interface is (via ifconfig
for instance).
In our example vm, the external interface is em0. Then add the following:
ext_if=em0
anchor "blacklistd/*" in on $ext_if
Then start pf: service pf start
.
Now that our firewall is setup, we'll need to configure blacklistd. FreeBSD comes with an example blacklistd configuration at /etc/blacklistd.conf:
# $FreeBSD: releng/11.0/etc/blacklistd.conf 301226 2016-06-02 19:06:04Z lidl $
#
# Blacklist rule
# adr/mask:port type proto owner name nfail disable
[local]
ssh stream * * * 3 24h
ftp stream * * * 3 24h
smtp stream * * * 3 24h
submission stream * * * 3 24h
#6161 stream tcp6 christos * 2 10m
* * * * * 3 60
# adr/mask:port type proto owner name nfail disable
[remote]
#129.168.0.0/16 * * * = * *
#6161 = = = =/24 = =
#* stream tcp * = = =
As you can see, under the [local]
block we have services we'd like to monitor,
the number of failed attempts to trigger filtering and how long to filter future requests.
Under the [remote]
block we have some examples of how we could handle remote hosts.
In the first example, any of the IPs belonging to the 129.168.0.0/16 subnet are skipped from being blocked from any service due to the '*' in the disable column.
Let's tweak this a bit. Replace /etc/blacklistd.conf with our simplified one below:
# adr/mask:port type proto owner name nfail disable
[local]
ssh stream * * * 1 1m
# adr/mask:port type proto owner name nfail disable
[remote]
This isn't practical, but it is helpful for testing that blacklistd is working correctly.
With the file in place, enable blacklistd with sysrc blacklistd_enable="YES"
and then start it:
service blacklistd start
.
There is one last thing we need to do, and that is tell sshd to notify blacklistd when authorization failures occur.
To do so, add UseBlacklist yes
to your /etc/ssh/sshd_config.
Then reload sshd with service sshd reload
.
Try logging into the box with an invalid password. If things are working, you won't even get prompted to try again.
PF will have already added a rule to the blacklistd/ssh anchor for the offending IP.
It's worth mentioning that depending on how you've configured your ssh client, you may trigger an aggressive blacklist configuration like the one above prematurely.
In other words, an ssh-agent may send a private key before you even get the chance to send an (in)valid password.
In practice, you should account for this in your nfails column, but for this demo, appending -o 'PubkeyAuthentication no'
to your ssh command should suffice.
You can verify that blacklistd has blocked the address by running (from another host) the command blacklistctl dump -b
:
address/ma:port id nfail last access
10.0.0.16/32:22 OK 1/1 2017/05/29 09:53:59
When the time on this ban expires, our IP will automatically be removed by blacklistd from the firewall's filter, and we can try again.
We've only covered a few of the basics here, so be sure to check out the manpages ( blacklistd, blacklistd.conf, blacklistctl ) for more.