FreeBSD: Fail2Ban With PF Doesn't Kill Connection State
I’m using Fail2Ban on my FreeBSD servers to parse log files for abusive behaviour and block the respective IPs. The PF current official action only adds the IP to a table without killing the existing states. If an attacker simply keeps open the connection and re-uses it (a common occurrence), PF won’t apply the block. Block rules are understandably applied when a connection is initially attempted, not after.
This issue is known since at least 2017.
The Fail2Ban developers deem the proposed solution of dropping all states for a
detected IP as too drastic an implementation. The argument being that PF should
support dropping just states of specific ports of detected IPs. Since PF,
respectively pfctl
, cannot do that, we’re left with an incomplete solution.
My position is that when I detect any kind of abusive traffic that warrants blocking the originating IP, the source has forfeited its privilege to connect to my server at all. Some may view that as an extreme measure. Certainly large providers or services would not want to ban potentially shared IPs used by many people on the basis of one abusive actor. But then again, I cannot imagine them using Fail2Ban to parse enormous amounts of log files. There are more scalable and performant solutions available. It’s a solution for low to medium traffic servers.
Since no resolution towards official inclusion is in sight, I’ve created my own action, incorporating the suggested fix. The changes are tiny.
First, copy the original file.
cp /usr/local/etc/fail2ban/action.d/pf.conf /usr/local/etc/fail2ban/action.d/pf_kill.conf
Change the ban action. Here’s the diff:
--- pf.conf 2022-11-09 16:46:15.000000000 +0100
+++ pf_kill.conf 2023-06-20 12:54:50.917207000 +0200
@@ -66,7 +66,7 @@
# <time> unix timestamp of the ban time
# Values: CMD
#
-actionban = <pfctl> -t <tablename>-<name> -T add <ip>
+actionban = <pfctl> -t <tablename>-<name> -T add <ip> && <pfctl> -k <ip>
# Option: actionunban
Then adjust the ban action in your jail.local
file.
banaction = pf_kill[actiontype=<allports>]
That’s it. Since you’ve created a new action, it won’t be overwritten by package updates.
Restart Fail2Ban and enjoy instant blocking.