fail2ban filter: Block based on mod_security failures

I wanted to write a fail2ban filter which watched my mod_security log file, and added repeat offenders to the firewall block list. I looked at several tutorials/howtos about writing filters, and they were all amazingly complicated, and most of them devoid of useful examples.

After some experimentation, I got something working, and it was remarkably simple. So here goes.

First, the mod_security rule itself.

# Block malicious bots
SecRule REQUEST_HEADERS:User-Agent "@pmFromFile /etc/httpd/modsecurity.d/badbots.txt" "id:5000025,rev:1,severity:2,log,msg:'BAD BOT - Detected and Blocked. '"

The line that starts with SecRule is all one line.

badbots.txt is a text file containing the names of annoying/malicious bots. Specifically I noticed that almost all of the traffic to one of my sites was from a bot named ahrefbot which was making very suspicious requests.

Now, I have entries in my error log that look like:

[Wed Feb 19 16:29:44.363193 2020] [:error] [pid 19321:tid 140221286971136] [client 46.229.168.131:47466] [client 46.229.168.131] ModSecurity: Access denied with code 406 (phase 2). Matched phrase "SemrushBot" at REQUEST_HEADERS:User-Agent. [file "/etc/httpd/conf.d/vhosts/drbacchus.conf"] [line "33"] [id "5000025"] [rev "1"] [msg "BAD BOT - Detected and Blocked. "] [severity "CRITICAL"] [hostname "drbacchus.com"] [uri "/"] [unique_id "Xk1ieF8Z-mVmfnUdi8jliwAAAEA"]

(SemrushBot is another frequent offender.)

The important bits in that line are the client address, and the fact that this triggered the particular rule that I care about. I’ll come back to that in a second.

Step two is to create a new “Jail” in fail2ban. I did this by adding a block to the end of my /etc/fail2ban/jail.local file that looks like:

[modsec]
enabled = true
filter = modsec
action = iptables-multiport[name=ModSec, port="http,https"]
logpath = /var/log/httpd/drbacchus-ssl.error_log
bantime = 10800
maxretry = 1

This creates a jail named modsec. It points to a filter named modsec. It references the log file that I want to watch, and it specifies a ban time of 3 hours.

It’s also very aggressive in that it bans them the first time. You might want to be more lenient with other filters.

Finally, I define the filter itself, by creating a file called modsec.conf in my filter.d directory, with the regex that I wish to match in the referenced log file.

[definition]
failregex = [client <HOST>] ModSecurity: Access denied with code 406.+BAD BOT
ignoreregex =

The line that begins with ‘failregex’ is all one line – it’s just wrapped on your screen here.

The magic bit is the <HOST> which says “the IP address that I want to block will be *here*. The rest of the line is standard regex syntax.

The docs say that you want the regex to be as specific as possible, so that it doesn’t match unexpected things. In this case, I want anything that has the ModSecurity access denied message, followed by some stuff (.+) and BAD BOT from my modsec rule. Many of the examples online appear to have been written by people who were perhaps not very familiar with how regexes work, and so go a bit nuts with the special characters and stuff. That’s really not necessary.

Now, restart fail2ban, and watch the results with fail2ban-client status modsec