Update Fail2Ban apache-security.conf filter for security2 module

OVERVIEW

The apache-security.conf filter included with Fail2Ban might not detect Apache2.4 error log entries after updating from mod_security to the newer security2 module.

tl;dr version: You can get all the files you need in the fail2ban-filter-apache-security2 Github repository.

BACKGROUND

ModSecurity is an open source web application firewall for the Apache web server. After updating from mod_security to the security2 module, testing with the fail2ban-regex utility indicated that the Fail2Ban apache-security.conf filter was no longer catching log entries of hosts that were banned by ModSecurity.

> fail2ban-regex '[Thu Aug 17 12:06:18.252097 2017] [:error] [pid 24997:tid 140453366175488] [client 111.222.111.222:12345] [client 111.222.111.222] ModSecurity: Access denied with code 403 (phase 2). Operator GE matched 5 at TX:anomaly_score. [file "/usr/share/modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "57"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [severity "CRITICAL"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "example.com"] [uri "/"] [unique_id "W3XLKgozdh0AAGGlZmUAAAAH"]' /etc/fail2ban/filter.d/apache-modsecurity.conf

Running tests
=============

Use   failregex filter file : apache-modsecurity, basedir: /etc/fail2ban
Use      single line : [Thu Aug 17 12:06:18.252097 2017] [:error] [pid 24...


Results
=======

Failregex: 0 total

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [1] (?:DAY )?MON Day 24hour:Minute:Second(?:\.Microseconds)?(?: Year)?
`-

Lines: 1 lines, 0 ignored, 0 matched, 1 missed
[processed in 0.00 sec]

|- Missed line(s):
|  [Thu Aug 17 12:06:18.252097 2017] [:error] [pid 24997:tid 140453366175488] [client 111.222.111.222:12345] [client 111.222.111.222] ModSecurity: Access denied with code 403 (phase 2). Operator GE matched 5 at TX:anomaly_score. [file "/usr/share/modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "57"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [severity "CRITICAL"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "example.com"] [uri "/"] [unique_id "W3XLKgozdh0AAGGlZmUAAAAH"]
`-

These test results show that the date/time portion of the sample log entry was captured, but the rest of the line was not. Taking a look at the /etc/fail2ban/filter.d/apache-modsecurity.conf file we find that the regular expression being used is…

[Definition]
failregex = ^%(_apache_error_client)s ModSecurity:\s+(?:\[(?:\w+ \"[^\"]*\"|[^\]]*)\]\s*)*Access denied with code [45]\d\d

This does not tell us the entire story. We need to see what %(_apache_error_client)s expands into to get a complete picture. That string is defined in /etc/fail2ban/filter.d/apache-common.conf as…

_apache_error_client = \[\] \[(:?error|\S+:\S+)\]( \[pid \d+(:\S+ \d+)?\])? \[client (:\d{1,5})?\]

Putting these two together gives us:

\[\] \[(:?error|\S+:\S+)\]( \[pid \d+(:\S+ \d+)?\])? \[client (:\d{1,5})?\] ModSecurity:\s+(?:\[(?:\w+ \"[^\"]*\"|[^\]]*)\]\s*)*Access denied with code [45]\d\d

Looking back at the beginning of the sample log entry we can see that the client IP address is now listed twice by ModSecurity2. The first instance shows the IP address and the port number, the second instance show only the IP address. Because of this, the old regular expression will fail to catch ModSecurity2 log entries…

[Thu Aug 17 12:06:18.252097 2017] [:error] [pid 24997:tid 140453366175488] [client 111.222.111.222:12345] [client 111.222.111.222] ModSecurity: Access denied with code 403 ...

Next, use the fail2ban-regex utility to test a new regular expression against the sample log entry…

> fail2ban-regex '[Thu Aug 17 12:06:18.252097 2017] [:error] [pid 24997:tid 140453366175488] [client 111.222.111.222:12345] [client 111.222.111.222] ModSecurity: Access denied with code 403 (phase 2). Operator GE matched 5 at TX:anomaly_score. [file "/usr/share/modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "57"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [severity "CRITICAL"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "example.com"] [uri "/"] [unique_id "W3XLKgozdh0AAGGlZmUAAAAH"]' '\[\] \[(:?error|\S+:\S+)\]( \[pid \d+(:\S+ \d+)?\])? \[client (:\d{1,5})?\](?: \[client [\d\.:]+\])? ModSecurity:\s+(?:\[(?:\w+ \"[^\"]*\"|[^\]]*)\]\s*)*Access denied with code [45]\d\d'

Running tests
=============

Use   failregex line : \[\] \[(:?error|\S+:\S+)\]( \[pid \d+(:\S+ \d+)?\]...
Use      single line : [Thu Aug 17 12:06:18.252097 2017] [:error] [pid 24...


Results
=======

Failregex: 1 total
|-  #) [# of hits] regular expression
|   1) [1] \[\] \[(:?error|\S+:\S+)\]( \[pid \d+(:\S+ \d+)?\])? \[client (:\d{1,5})?\](?: \[client [\d\.:]+\])? ModSecurity:\s+(?:\[(?:\w+ \"[^\"]*\"|[^\]]*)\]\s*)*Access denied with code [45]\d\d
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [1] (?:DAY )?MON Day 24hour:Minute:Second(?:\.Microseconds)?(?: Year)?
`-

Lines: 1 lines, 0 ignored, 1 matched, 0 missed
[processed in 0.00 sec]

Now the regular expression is catching the log entry. By adding (?: \[client [\d\.:]+\])? after the original client IP address detection the regular expression will match log entries whether or not they contain the second instance of the client IP address, but any matching data will be ignored.


APACHE-SECURITY2 FILTER

Now let’s put it all together in a new file called /etc/fail2ban/filter.d/apache-security2.conf so that any updates to Fail2Ban will not overwrite the new regular expression. To minimize customization of the file, we will simply insert (?: \[client [\d\.:]+\])? immediately after the original ^%(_apache_error_client)s

# Fail2Ban apache-security2 filter
#
# Author: Kazimer Corp
# Author URL: https://www.kazimer.com
#
# Ref: https://github.com/SpiderLabs/ModSecurity/wiki/ModSecurity-2-Data-Formats


[INCLUDES]
# Read common prefixes. If any customizations available -- read them from
# apache-common.local
before = apache-common.conf

[Definition]
# Option:  failregex
# Notes.:  Uncomment second filter line if you wish to block requesters using a numeric IP address in the host header.

failregex = ^%(_apache_error_client)s(?: \[client [\d\.:]+\])? ModSecurity:\s+(?:\[(?:\w+ \"[^\"]*\"|[^\]]*)\]\s*)*Access denied with code [45]\d\d (?:.*)$

ignoreregex =

Next, disable the old apache-modsecurity jail and create a new apache-security2 jail in your /etc/fail2ban/jail.local file…

[apache-modsecurity]
enabled = false

[apache-security2]
port     = http,https
logpath  = %(apache_error_log)s
#action   = %(action_cfv4)s ; Change action if using CloudFlare
maxretry = 4
enabled = true

The commented out action variable can be used to work with the Fail2Ban CloudFlare action if you are using that service.

Finally, restart the service…

sudo service fail2ban restart

You can get the latest version of the Fail2Ban Apache ModSecurity2 filter in the GitHub Repository.

ADDITIONAL PROTECTION FOR NUMERIC HOST VIOLATIONS

If your web server is configured with virtual hosts, and does not respond to requests using a numeric IP address as the host name, ModSecurity will only issue a warning. To block these types of attacks, we can add a second regular expression to filter these ModSecurity log entries, as shown below…

[Definition]
failregex = ^%(_apache_error_client)s(?: \[client [\d\.:]+\])? ModSecurity:\s+(?:\[(?:\w+ \"[^\"]*\"|[^\]]*)\]\s*)*Access denied with code [45]\d\d (?:.*)$
            ^%(_apache_error_client)s(?: \[client [\d\.:]+\])? ModSecurity: Warning. (?:.*) \[msg \"Host header is a numeric IP address\"\] (?:.*)$

DEBUGGING

If fail2ban won’t restart after you have edited configuration files, you can try manually starting it with verbose messaging enabled to find out where it is failing as it is starting up.

fail2ban-client -vvv -x start

Once you have fixed all errors in your configuration files, restart it as a service.

fail2ban-client -x stop
service fail2ban start