published: 2022-10-01
title: My first custom Fail2Ban filter
tags:
previous [‘Set email priority with mutt’] next [‘What is stopping us from using free software?’] parent directory [‘Blog’]
On my servers that are meant to be world-accessible, the first things I set up are the firewall and Fail2Ban[3], a service that updates my
fail2ban [‘Fail2Ban’] firewall rules automatically to reject requests from IP addresses that have failed repeatedly before. The ban duration and number of failed attempts that trigger a ban can easily be customized; that way, bots- and hacker attacks that try to break into my system via brute force and trial and error can be blocked or at least delayed very effectively.
Luckily, many pre-defined and modules and filters already exist that
I can use to secure my offered services. To set up a jail for
sshd
for instance and do some minor configurations, I only
need a few lines in my /etc/fail2ban/jail.local
file:
[DEFAULT]
bantime = 4w
findtime = 1h
maxretry = 2
ignoreip = 127.0.0.1/8 192.168.0.1/24
[sshd]
enabled = true
maxretry = 1
findtime = 1d
Just be aware that you should not change
/etc/fail2ban/jail.conf
, as this will be overwritten by
fail2ban. If a jail.local
is not already present, create
one.
As you can see, I set some default options about how long IPs should
be banned and after how many failed tries. I also exclude local IP
ranges from bans, so I’ll not lock myself out every time I test a new
service or setting. However, for sshd
I even tighten the
rules a bit, since I only use public key authentication where I don’t
expect a single failure from a client that is allowed to connect. All
the others can happily be sent to jail.
It’s always a joy but also kind of terrifying to check the jail for the currently banned IPs; the internet is not what I would call a safe place.
sudo fail2ban-client status sshd
Status for the jail:
|- Filter
| |- Currently failed: 0
| |- Total failed: 211
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 2016
|- Total banned: 2202
`- Banned IP list: ...
Do identify IP addresses that should be banned, Fail2Ban scans the
appropriate log files for failed attempts with a regular expression, as
the sshd
module does with my
/var/log/auth.log
.
Like mentioned above, there are already quite some pre-defined
modules. For my nginx
reverse proxy the modules
nginx-botsearch
, nginx-http-auth
and
nginx-limit-req
are available; the log files they scan by
default is /var/log/nginx/error.log
.
However, having a look in my /var/log/nginx/access.log
I
regularly find lots of failed attempts that are probing my
infrastructure. They look like this:
118.195.252.158 - - [01/Oct/2022:02:08:59 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
118.195.252.158 - - [01/Oct/2022:02:08:59 +0200] "GET http://xxx.xxx.xxx.xxx:80/mysql/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
118.195.252.158 - - [01/Oct/2022:02:08:59 +0200] "GET http://xxx.xxx.xxx.xxx:80/pma/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
118.195.252.158 - - [01/Oct/2022:02:08:59 +0200] "GET http://xxx.xxx.xxx.xxx:80/db/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
...
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin-2.11.7/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin-2.11.4/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin-2.10.3/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin2/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/db/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin-2/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin-2.11.3/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/mysqladmin/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/myadmin/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin-2.11.1.2/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin-2.11.9.2/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/pma/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin-2.10.2/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin-2.10.0.2/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin-2.8.0.2/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/phpMyAdmin-2.11.0/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
42.236.120.51 - - [01/Oct/2022:02:26:57 +0200] "GET http://xxx.xxx.xxx.xxx:80/mysql/scripts/setup.php HTTP/1.0" 404 162 "-" "-"
...
185.183.122.143 - - [30/Sep/2022:01:19:48 +0200] "GET /wp-login.php HTTP/1.1" 404 134 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96"
198.98.59.132 - - [30/Sep/2022:01:51:59 +0200] "POST /boaform/admin/formLogin HTTP/1.1" 404 134 "http://xxx.xxx.xxx.xxx:80/admin/login.asp" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:71.0) Gecko/20100101 Firefox/71.0"
20.168.74.192 - - [30/Sep/2022:01:54:29 +0200] "GET /.env HTTP/1.1" 404 134 "-" "Mozilla/5.0 (Linux; U; Android 4.4.2; en-US; HM NOTE 1W Build/KOT49H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/11.0.5.850 U3/0.8.0 Mobile Safari/534.30"
20.168.74.192 - - [30/Sep/2022:01:54:29 +0200] "GET /_profiler/phpinfo HTTP/1.1" 404 134 "-" "Mozilla/5.0 (Linux; U; Android 4.4.2; en-US; HM NOTE 1W Build/KOT49H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/11.0.5.850 U3/0.8.0 Mobile Safari/534.30"
20.168.74.192 - - [30/Sep/2022:01:54:30 +0200] "GET /config.json HTTP/1.1" 404 134 "-" "Mozilla/5.0 (Linux; U; Android 4.4.2; en-US; HM NOTE 1W Build/KOT49H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/11.0.5.850 U3/0.8.0 Mobile Safari/534.30"
20.168.74.192 - - [30/Sep/2022:01:54:30 +0200] "GET /.git/config HTTP/1.1" 404 134 "-" "Mozilla/5.0 (Linux; U; Android 4.4.2; en-US; HM NOTE 1W Build/KOT49H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/11.0.5.850 U3/0.8.0 Mobile Safari/534.30"
I don’t use PhpMyAdmin and I don’t host a wordpress site (requests to
wp-login and wp-admin are pretty common) and I would prefer to ban IPs
that scan my infrastructure for these services. So I wrote a new filter
to scan my nginx/access.log
file for requests of that
kind.
In /etc/fail2ban/filters.d/nginx-access.conf
I added the
following definition:
[Definition]
_daemon = nginx-access
failregex = (?i)^<HOST> .*(wp-login|xmlrpc|wp-admin|wp-content|phpmyadmin|mysql).* (404|403)
(?i)
makes the whole regular expression case
insensitive, so it will capture phpmyadmin
and
PhpMyAdmin
equally.^<HOST>
will look from the start of each line to
the first space for the IP address. <HOST>
is a
defined capture group from Fail2Ban, that must be present in
failregex
es to let Fail2Ban know who to ban..*
matches any character, and an arbitrary number of
them(wp-login|wp-admin...)
these are the request snippets
to look for; in parentheses and separated with the pipe operator, it
will look for matches of either of the given strings.(404|403)
are http responses for “file/page not found”
and “forbidden”. So if these pages are not available or not meant to be
accessed, this rule will be triggered.In my jail.local
I add the following section to use the
new filter:
[nginx-access]
enabled = true
port = http,https
filter = nginx-access
logpath = /var/log/nginx/access.log
Restart the fail2ban service
(e.g. systemctl restart fail2ban
) to enable the new
rule.
I started with only a few keywords to filter, but the used regular expression can easily be expanded by further terms.