Intrusion detection with blacklistd & PF

Hi gang!

Editorial

When it comes to security then I have a heavy preference for using features and utilities which are part of the base system. My reasoning for that is really very simple: in your time of need you will most likely have full access to "a" FreeBSD environment to help you sort out the mess you're in. You can be assured that all the tools which make up the base system are at your disposal, one way or the other. But can you also say the same for any 3rd party tools?

So when I learned that blacklistd, a real-time intrusion detection system, was going to become part of the base system I was immediately hooked on the idea. The only problem: it was going to be using my firewall directly, and I wasn't really a fan of the idea at first. For me the firewall was basically the last line of defense. I didn't mind that applications would mess with tcpwrappers (/etc/hosts.allow), because that was also used on the application level. The firewall would trump all, and that should be fully under my control.

Yet when I learned more of the things you can do with PF and how FreeBSD manages to secure the whole setup with blacklistd I was convinced. So I figured... why not a brief guide, it's been a while anyway. As always my guide is more than merely a set of instructions on how to set it all up, I'm also trying to reflect a little on the theory behind it.

What is blacklistd?

Blacklistd is a so called Intrusion Detection System ("IDS"), but one operating in real-time. It found its origins in NetBSD and eventually was added to FreeBSD when version 11 got released. What makes it so special in comparison with some of the other available software is the fact that many programs on FreeBSD (and within the Ports collection as well) have been provided with support to communicate with blacklistd directly. So instead of trying to listen to sockets or monitor logfiles, all that blacklistd has to do is wait for any software to inform it about failed login attempts. If a certain threshold has been reached within a given time then the offender(s) will be blocked for a period determined by the administrator. This is usually done by adding the offender(s) to the systems firewall. Which is where PF comes into play...

PF - the OpenBSD firewall

PF is the standard firewall on OpenBSD and OpenBSD, in case you didn't know, is a BSD environment which has completely focused itself on providing the most secured environment possible. Sometimes even up to a point where it will gladly sacrifice ease of use in favor for better security. Funnily enough though that was not why I grew such a serious liking to PF.

This may sound a bit strange, especially after reading the above, but fact of the matter is that in my opinion PF is actually the easiest and most flexible firewall out of the three available on FreeBSD. In all fairness though: the only other firewall I actively used other than PF is IPF, the firewall as used on Sun Solaris. I've only briefly read up about IPFW yet never bothered myself to actively study it.

The one thing I admire the most about PF is that it provides many ways to influence the firewall yet without having to directly access or affect the main underlying firewall rules. PF provides 2 main features which make this possible.

First are tables. A table is basically a list of IP addresses and the way to handle those is determined by your ruleset. For example; on my servers I always keep a so called "shitlist" active, this is basically a table which contains a ton of offending IP addresses which I'd rather keep out:
Code:
table <shitlist> persist file "/etc/pf/smtp" file "/etc/pf/shitlist"
....
block quick from <shitlist>
As you can see I filled the list with a series of IP addresses from two separate files, and then I told my firewall to immediately and fully block any IP addresses residing on that list. Once you're on this list you'll be gone for quite some time.

Anchors ahoy!

The second feature, and the main reason why I put so much trust into this design despite it accessing my firewall directly, are anchors.

An anchor is basically a completely separate firewall section on top (or 'besides') your main firewall section. It can contain its own rules, its own tables, almost everything which you might find in the main ("regular") firewall section. Anchors can fully coexist and will never have to bother each other. So as long as I make sure that blacklistd utilizes an anchor then there's no way that it will ever affect my main firewall rules directly. ... sort off.

But is this really safe?

If you're a bit paranoid like me (which is a good thing when dealing with security) then this may not seem very safe at all. The first problem is that anchors get defined before the filtering rules. Why is that a problem? Well...
Code:
set ext_if="em0"
anchor "blacklistd/*" in on $ext_if
block on $ext_if
So here we have an anchor which is linked to incoming traffic on em0. That is followed by a block rule which basically blocks everything on em0. First the anchor gets processed, then the other filtering rules, like our 'block all' command.

So now imagine the following command: # echo "pass in quick from 1.2.3.4 to any" | pfctl -a blacklistd -f -. "oops". Now we have pretty much bypassed the previously shown block rule. So, anchors could overrule our main rules, and that doesn't sound very safe.

Fortunately for us the developers thought of this and the firewall isn't handled within blacklistd as some kind of 'black box'. Instead it uses a predetermined shell script; by default this is /usr/libexec/blacklistd-helper. So if you need full insurances that nothing weird can happen then simply study this shell script up front or, also something to consider, write your own and use that one instead. As you can see all this has little to do with taking a leap of faith or such. Instead the whole setup strictly follows a predetermined routine. Keep the shell script safe and you keep your firewall safe.

Setting it up

As mentioned above the first thing you need to do is provide PF with an anchor, this should be called blacklistd by default. So add something like this to your /etc/pf.conf file:
Code:
anchor "blacklistd/*" in on em0
This will tell PF that it should process both the blacklistd anchor as well as anything else that sits 'below' it.

Next step: /etc/blacklistd.conf. There are two sections you need to sort out: local and remote. Local consists of the rule(s) which you want to use to block incoming traffic whereas remote is to whitelist certain hosts and prevent them from getting blocked. You can make this as complex or straightforward as you wish.

For example:
Code:
# Blacklist rule
# adr/mask:port type    proto   owner           name    nfail   disable
[local]
ssh             stream  *       *               *       3       48h
ftp             stream  *       *               *       3       24h
smtp            stream  *       *               *       3       24h

# adr/mask:port type    proto   owner           name    nfail   disable
[remote]
10.0.1.25       *       *       *               =       *       *
So what do we have here? When I get three login failures on the SSH server then the offender will be banned for 48 hours. When this happens with FTP or SMTP then the ban will be 24 hours. And finally 10.0.1.25 is fully exempt from banning. So what if we wanted to only whitelist him from, say, FTP logins but not SSH? Easy: simply specify the protocol. In the above example I'd use something like: 10.0.1.25:ftp to specify the remote host. Now the whitelist would only apply when FTP is being used.

And that's pretty much it. Once you're fully set you should make sure that blacklistd is enabled and started, and then you should simply wait. To check:

Code:
root@zefiris:/home/peter # sysrc blacklistd_enable
blacklistd_enable: YES
root@zefiris:/home/peter # service blacklistd status
blacklistd is running as pid 1132.
We're secured 8)

So how does this work?

Simple. If you need to check if something actually happened on your host use: blacklistdctl dump. This will show you all the currently tracked hosts, how many attempts were made and when they tried for the last time. If you're only interested in the banned hosts then use the -b parameter.

But how can we be sure that this really worked? Well, simple, we check:

Code:
root@breve:/home/peter # pfctl -a blacklistd -s Anchors
  blacklistd/21
root@breve:/home/peter # pfctl -a blacklistd/21 -s rules
block drop in quick proto tcp from <port21> to any port = ftp
root@breve:/home/peter # pfctl -a blacklistd/21 -s Tables
port21
root@breve:/home/peter # pfctl -a blacklistd/21 -t port21 -T show
root@breve:/home/peter #
Knowing that we need to use the blacklistd anchor that's where I checked first. You might be tempted to check for rules or tables but that will gain you nothing. So if you then check for anchors you'll get the above.

And once you established this situation you also know how to check for banned hosts without the help from blacklistd. After all: those will merely be added to the port21 table which resides in the blacklistd/21 anchor (so basically: the blacklistd anchor which contains the 21 anchor) and once no longer needed the hosts will be removed again.

It is important to know how to sort this out in the firewall because you cannot remove banned hosts using the default blacklistd tools. For example you can't "pardon" a host just like that, for that you'd need to change the firewall manually yourself. And optionally add the host to the whitelist section.

And there you have it....

It's quite hot over here right now so I hope I haven't made too many weird topoes. If so then rest assured that those will be fixed soon. Anyway, I hope this can help some of you out a bit.
 
Some HOWTOs make life so simple, things easy to understand. This is one such HOWTO. Thank you.
I only realized that the command in FreeBSD-12.1 is blacklistdctl (without the "d").
 
Back
Top