The following web server security configuration is described:
ngx_http_limit_req
detects the excess of the request limit;error.log
, which Nginx uses to report on the excesses;There is a script mitigator.py
(download)
to manage MITIGATOR, in particular to temporarily block an IP address via
the MITIGATOR API. If necessary, the script can be modified
to perform any other actions on MITIGATOR. The script uses only the
standard modules and works with Python 2.7+ and Python 3.
To run the script, you need the MITIGATOR account and the protection policy ID
(for example, 42
in the URL .../policies/42
when accessing MITIGATOR).
The script also accepts IP and blocking time in seconds.
The description of all parameters is printed with the --help
(-h
) option.
The ngx_http_limit_req
module is built into Nginx and allows you to limit
the number of requests per second (RPS) via the Nginx configuration.
Request limit refers to zone. Typically, the zone is the client’s IP (eg. requests from it are limited) or the URI on the site (requests to it are limited), but more complex combinations of request parameters are also possible
Zones are described in the http
context. Let’s describe a perip
(per IP) zone
that can track a 10 RPS limit violation for any one of 10 million IP addresses.
To do this, add the line to /etc/nginx.conf
through an intermediate file:
mkdir -p /etc/nginx/nginx.conf.d
cat > /etc/nginx/conf.d/limit-req.conf <<'END'
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
END
To apply this limit to a part of the site (the location
context) and to prevent access
from one IP to the page more than 10 times per second, use the following directive in
/etc/nginx/sites-available/default
(if requests are limited for the default site):
server {
...
location / {
...
limit_req zone=perip burst=20 nodelay;
}
}
Here burst=20
allows bursts of activity up to 20 RPS
(but no more than 10 RPS on average, as described for the zone), and nodelay
means that requests over the limit are dropped, and do not wait in line (relevant for DDoS).
Let’s update Nginx config:
nginx -s reload
When the limit is exceeded, lines like this appear in error.log
:
2019/01/11 09:27:12 [error] 155#155: *142 limiting requests, excess: 20.200 by zone "perip", client: 10.0.1.254, server: _, request: "GET / HTTP/1.1", host: "10.0.2.254"
The fail2ban utility analyzes the logs and, if it finds the specified signs, performs blocking actions. Fail2ban also allows you to manage lists of blocked addresses.
Let’s place the blocking script:
install mitigator.py /usr/local/bin
Let’s create a new fail2ban action that will call the script:
cat >/etc/fail2ban/action.d/mitigator.conf <<'END'
[Definition]
actionban = \
/usr/local/bin/mitigator.py \
--server "<server>" \
--user "<user>" --password "<password>" \
--no-verify \
--policy <policy> \
tbl block --ip <ip> --time <bantime>
actionunban = \
/usr/local/bin/mitigator.py \
--server "<server>" \
--user "<user>" --password "<password>" \
--no-verify \
--policy <policy> \
tbl unblock --ip <ip>
END
Labels <server>
, <user>
, <password>
, <policy>
, <ip>
and <bantime>
must be written as is - these are parameters that will be automatically replaced
with correct values. The --no-verify
key is needed if MITIGATOR works with
a self-signed certificate and you need to disable its verification.
Let’s create a restriction (jail) that will block IP based on entries in error.log
:
cat >>/etc/fail2ban/jail.local <<END
[nginx-limit-req]
enabled = true
logpath = %(nginx_error_log)s
banaction = mitigator[server=mitigator.local, user=admin, password=admin, policy=42, bantime=600]
END
The nginx-limit-req
module comes with fail2ban and finds the required lines.
The banaction=...
line specifies the actual parameters of the action: server,
username, password, policy ID. For example, the blocking time is 10 minutes.
Let’s reboot fail2ban rules:
fail2ban-client reload
Traffic must go through MITIGATOR, general protection and protection of the selected policy must be enabled.
You can simulate an attack with the httperf
utility:
httperf --server 192.0.2.20 --num-conn 100
With a successful setup, a few seconds after running httperf
on the server,
you can see that fail2ban has worked:
$ fail2ban-client status nginx-limit-req
Status for the jail: nginx-limit-req
|- Filter
| |- Currently failed: 1
| |- Total failed: 65
| `- File list: /var/log/nginx/error.log
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: 192.0.2.10
On MITIGATOR in the TBL policy card you can check that the attacker’s IP (192.0.2.10 in the example) is on the list of temporarily blocked.
You can unblock IP either through MITIGATOR or fail2ban:
fail2ban-client set nginx-limit-req unbanip 192.0.2.10
If you do not need to block an IP through fail2ban, and it is undesirable to add it to the «White list» (WL or TWL) on MITIGATOR, you can ignore the IP at the fail2ban level:
fail2ban-client set nginx-limit-req addignoreip 192.0.2.10
If fail2ban did not work, errors can be observed in its log:
tail -f /var/log/fail2ban/fail2ban.log