Howto Fail2Ban
- Howto Fail2Ban
- Installation
- Configuration
- Fonctionnement
- Administration
- Ajouter ses propres jails
- Exemples
- HAProxy
- webapps
- Munin
- FAQ
- Fail2Ban supporte l’IPv6 ?
- Attaque via un utilisateur
- Comment mettre en liste blanche certaines adresses IP ?
- Comment bannir une ip manuellement ?
- Erreur “fail2ban.actions.action: ERROR” avec jail au nom trop long
- Taille de la base de données SQLite et durée de conservation des bans
- ignorecommand
- Google Bot et crawlers
- Documentation : https://github.com/fail2ban/fail2ban/wiki
- Rôle Ansible : https://forge.evolix.org/projects/ansible-roles/repository/show/fail2ban
- Statut de cette page : test / bookworm
Howto Fail2Ban
Fail2Ban est un outil pour limiter les attaques par brute force : il scanne en permanence des journaux pour détecter des anomalies répétitives et bannir les adresses IP coupables via IPTables. Nous l’utilisons souvent pour les erreurs d’authentification répétées sur des services publics comme SMTP, POP, IMAP ou FTP. On peut également être amené à l’utiliser pour SSH mais c’est plus rare car il est évidemment préférable de limiter directement l’accès via IPTables.
Installation
# apt install fail2ban
# fail2ban-client -V
1.0.2
# systemctl status fail2ban
● fail2ban.service - Fail2Ban Service
Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled; preset: enabled)
Active: active (running) since Wed 2024-07-17 10:31:49 CEST; 2 weeks 4 days ago
Docs: man:fail2ban(1)
Main PID: 2244969 (fail2ban-server)
Tasks: 5 (limit: 4686)
Memory: 13.8M
CPU: 24min 26.161s
CGroup: /system.slice/fail2ban.service
└─2244969 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
Configuration
Fichiers de configuration :
/etc/fail2ban
├── fail2ban.conf
├── jail.conf
├── jail.local
├── jail.d
│ └── defaults-debian.conf
├── action.d
│ ├── apf.conf
│ ├── badips.conf
│ ├── blocklist_de.conf
│ […]
├── fail2ban.d
├── filter.d
│ ├── 3proxy.conf
│ ├── apache-auth.conf
│ ├── apache-badbots.conf
│ […]
├── paths-common.conf
├── paths-debian.conf
└── paths-opensuse.conf
La configuration principale est dans
/etc/fail2ban/fail2ban.conf
:
[Definition]
loglevel = INFO
logtarget = /var/log/fail2ban.log
syslogsocket = auto
socket = /var/run/fail2ban/fail2ban.sock
pidfile = /var/run/fail2ban/fail2ban.pid
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 86400
Nous surchargeons cette configuration en créant
/etc/fail2ban/fail2ban.local
:
[DEFAULT]
loglevel = NOTICE
dbfile = None
Notons que l’on désactive le stockage dans une base SQLite permettant d’avoir des données persistantes lors d’un redémarrage. En effet, cette base SQLite peut vite grossir (surtout qu’un bug jusqu’à Debian 10 empêche la base de se purger, cf FAQ plus bas).
Par défaut Fail2Ban a des variables pour le chemin des logs
classiques, permettant de les utiliser via logpath
. Ainsi,
ces variables sont définies par
/etc/fail2ban/paths-common.conf
surchargé par
/etc/fail2ban/paths-debian.conf
. Nous surchargeons avec nos
chemins personnalisés via
/etc/fail2ban/paths-overrides.local
:
[DEFAULT]
apache_error_log = /var/log/apache2/error.log
/home/*/log/error.log
Fail2Ban repose sur des règles de filtrage définies dans
/etc/fail2ban/filter.d/
et des actions dans
/etc/fail2ban/action.d/
.
On définit ensuite des jails, combinaisons d’une règle de filtrage et d’une ou plusieurs actions.
Voici la jail nommée sshd (attention, en Debian 8 elle se nommait ssh) :
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 5
findtime = 600
bantime = 600
Cette jail va surveiller le fichier /var/log/auth.log
via la règle de filtrage /etc/fail2ban/filter.d/sshd.conf
(implicite car la jail a le même nom). Si cela détecte la correspondance
5 fois en 10 minutes, il va utiliser l’action par défaut, à savoir
l’action /etc/fail2ban/action.d/iptables-multiport.conf
qui
va ajouter une règle iptables pour bannir le(s) port(s)
concerné(s) pendant 10 minutes.
Attention, cette jail sshd est activée par défaut à
l’installation via le fichier
jail.d/defaults-debian.conf
Les paramètres par défaut sont :
Note : en Debian 8, le paramètre par défaut de maxretry est
maxretry = 3
Les paramètres par défaut et un certain nombre de jails (non activées
à part sshd) sont définies dans
/etc/fail2ban/jail.conf
, si l’on veut ajouter ou modifier
des paramètres ou des jails, il faut utiliser le fichier
/etc/fail2ban/jail.local
ou des fichiers
/etc/fail2ban/jail.d/*.conf
Voici un extrait de la configuration
/etc/fail2ban/jail.local
que nous utilisons :
[DEFAULT]
ignoreip = 127.0.0.1/8 31.170.9.129 31.170.8.4 82.65.34.85 46.231.240.96 54.37.106.210 51.210.84.146
bantime = 10m
maxretry = 5
destemail = foo@example.com
# ACTIONS
banaction = iptables-multiport
action = %(action_)s
[sshd]
enabled = true
port = ssh,2222,22222
maxretry = 10
findtime = 10m
bantime = 10m
Pour dumper la configuration courante :
# fail2ban-client -d
['set', 'loglevel', 3]
['set', 'logtarget', '/var/log/fail2ban.log']
[...]
Fonctionnement
L’action que nous utilisons en général est
iptables-multiport
ou %(banaction_allports)s
ce qui signifie que des règles iptables
vont être injectées
pour bannir l’IP coupable.
Si la jail n’a jamais été déclenchée, aucune règle
iptables
n’est injectée.
Lorsqu’une jail est déclenchée la première fois, Fail2Ban va
iptables -I
dans la chaîne INPUT un renvoi vers une
nouvelle chaîne. Par exemple si la jail “roundcube” est délenchée la
première fois, on constatera :
Chain INPUT (policy DROP 13 packets, 560 bytes)
pkts bytes target prot opt in out source destination
2465 667K f2b-roundcube 6 -- * * 0.0.0.0/0 0.0.0.0/0
...
Chain f2b-roundcube (1 references)
pkts bytes target prot opt in out source destination
14 896 REJECT 0 -- * * 192.0.2.66 0.0.0.0/0 reject-with-port-unreachable
2416 664K RETURN 0 -- * * 0.0.0.0/0 0.0.0.0/0
Ce fonctionnement signifie que si l’on purge les règles
iptables
en place, il faudra restart Fail2Ban pour qu’il
réinitialise ses injections !
Administration
Fail2Ban est un démon en Python, on peut s’assurer qu’il tourne bien sur un système :
$ ps auwx | grep fail2ban
root 24173 0.00.2 185048 12176 ? Sl 16:16 0:03 /usr/bin/python /usr/bin/fail2ban-server -b -s /var/run/fail2ban/fail2ban.sock -p /var/run/fail2ban/fail2ban.pid
Commandes de bases
# fail2ban-client status
# fail2ban-client status ssh
# fail2ban-client unban 192.0.2.42
fail2ban-client
De nombreuses commandes sont possibles avec
fail2ban-client
pour lister des informations ou modifier
des paramètres.
On peut lister les jails actives :
# fail2ban-client status
Status
|- Number of jail: 1
`- Jail list: ssh
Et l’on doit les retrouver au niveau d’iptables :
# iptables -L -n
Pour lister l’état de la jail ssh :
# fail2ban-client status ssh
Status for the jail: ssh
|- filter
| |- File list: /var/log/auth.log
| |- Currently failed: 0
| `- Total failed: 6
`- action
|- Currently banned: 1
| `- IP list: 192.0.2.42
`- Total banned: 1
Pour dé-bannir l’adresse IP 192.0.2.42 de la jail ssh :
# fail2ban-client set ssh unbanip 192.0.2.42
Pour lister les informations de la jail ssh :
# fail2ban-client get ssh maxretry
6
# fail2ban-client get ssh findtime
600
# fail2ban-client get ssh bantime
600
# fail2ban-client get ssh ignoreip
These IP addresses/networks are ignored:
`- 127.0.0.1/8
# fail2ban-client get ssh addaction
iptables-multiport
# fail2ban-client get ssh failregex
The following regular expression are defined:
[...]
On pourra mettre à jour les paramètres d’une jail à chaud avec
set
. Ça évite un couteux redémarrage de Fail2ban.
# fail2ban-client set ssh bantime 300
Ajouter ses propres jails
On peut bien sûr ajouter ses propres jails en ajoutant dans
le fichier jail.local
, un exemple :
[custom-1234]
enabled = true
port = http,https
filter = accesslog-custom-1234
logpath = /var/log/access.log
maxretry = 5
findtime = 180
bantime = 86400
ignoreip = 127.0.0.1/8 192.0.2.42
action = iptables-multiport
sendmail[dest=jdoe@example.com]
Attention : en cas d’action liée à iptables (ce qui est le défaut), le nom de la jail ne doit pas excéder 20 caractères !
Règles de filtrage
Les règles de filtrage sont définies dans
/etc/fail2ban/filter.d/
:
3proxy.conf apache-noscript.conf couriersmtp.conf exim.conf lighttpd-auth.conf openwebmail.conf proftpd.conf selinux-ssh.conf squid.conf webmin-auth.conf
apache-auth.conf apache-overflows.conf cyrus-imap.conf exim-spam.conf mysqld-auth.conf pam-generic.conf pure-ftpd.conf sendmail-auth.conf sshd.conf wuftpd.conf
apache-badbots.conf assp.conf dovecot.conf freeswitch.conf nagios.conf perdition.conf qmail.conf sendmail-reject.conf sshd-ddos.conf xinetd-fail.conf
apache-common.conf asterisk.conf dropbear.conf groupoffice.conf named-refused.conf php-url-fopen.conf recidive.conf sieve.conf suhosin.conf
apache-modsecurity.conf common.conf ejabberd-auth.conf gssftpd.conf nginx-http-auth.conf postfix.conf roundcube-auth.conf sogo-auth.conf uwimap-auth.conf
apache-nohome.conf courierlogin.conf exim-common.conf horde.conf nsd.conf postfix-sasl.conf selinux-common.conf solid-pop3d.conf vsftpd.conf
On peut écrire ses propres règles de filtrage en s’appuyant sur les expressions régulières Python pour détecter la variable .
Exemple d’une règle :
Pour tester un filtre :
# fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/evolix-test.conf
Afin d’obtenir des informations supplémentaires - dans le cas où on
cherche les heures de connexion d’une adresse IP particulière - on peut
utiliser les options -v
et/ou
--print-all-matched
:
# fail2ban-regex -v --print-all-matched /var/log/auth.log /etc/fail2ban/filter.d/evolix-test.conf
On peut aussi tester via le nom d’un filtre (avec le mode choisi) :
# fail2ban-regex /var/log/mail.log postfix[mode=aggressive]
On peut aussi tester avec une date :
$ fail2ban-regex '2017-05-20 10:10:10 foo' 'warning: \[<HOST>\]: authentication failed:'
[...]
Failregex: 0 total
[...]
$ fail2ban-regex '2017-05-20 10:10:10 [192.0.2.42]: authentication failed:' '\[<HOST>\]: authentication failed:'
[...]
Failregex: 1 total
|- #) [# of hits] regular expression
| 1) [1] \[<HOST>\]: authentication failed:
[...]
Explications détaillées à propos des regex pour Fail2Ban : https://github.com/fail2ban/fail2ban/wiki/Developing-Regex-in-Fail2ban
Actions
Les actions sont définies dans /etc/fail2ban/action.d/
:
apf.conf firewallcmd-ipset.conf iptables.conf iptables-xt_recent-echo.conf osx-ipfw.conf sendmail-whois-lines.conf
badips.conf firewallcmd-new.conf iptables-ipset-proto4.conf mail-buffered.conf pf.conf shorewall.conf
blocklist_de.conf hostsdeny.conf iptables-ipset-proto6-allports.conf mail.conf route.conf ufw.conf
bsd-ipfw.conf ipfilter.conf iptables-ipset-proto6.conf mail-whois.conf sendmail-buffered.conf
complain.conf ipfw.conf iptables-multiport.conf mail-whois-lines.conf sendmail-common.conf
dshield.conf iptables-allports.conf iptables-multiport-log.conf mynetwatchman.conf sendmail.conf
dummy.conf iptables-blocktype.conf iptables-new.conf osx-afctl.conf sendmail-whois.conf
Si l’on ne précise pas d’action, ça sera par défaut
iptables-multiport (définie dans jail.conf
) :
On peut aussi vouloir bloquer complètement l’adresse IP coupable :
banaction = %(banaction_allports)s
Exemple pour bannir et déclencher l’envoi d’un email :
Pour simuler les actions sans rien faire :
Cela va écrire dans le fichier
/var/run/fail2ban/fail2ban.dummy
mais surtout dans les logs
où l’on verra les INFO/NOTICE de la jail en question.
À noter qu’on peut définir le délai de « ban » via
bantime
, et l’on peut définir que ce temps sera plus grand
si l’IP coupable recommence via les options
bantime.increment = true
#bantime.factor = 1
#bantime.multipliers = 1 2 4 8 16 32 64
bantime.multipliers = 1 5 30 60 300 720 1440 2880
Cela va permettre d’avoir des jails plus agressives mais avec un temp de « ban » court pour la 1ère fois, ce temps ira ensuite en augmentant.
À noter qu’il existe aussi la jail “recidive” qui va simplement se
contenter de surveiller /var/log/fail2ban.log
:
[recidive]
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = 1w
findtime = 1d
Exemples
SSH invalid user
Le but de cette règle est de bannir immédiatement si une connexion SSH a lieu sur un utilisateur invalide. Très efficace contre les robots qui font des bruteforce massivement distribués.
On ajoute la règle filter.d/sshd-invaliduser.conf
:
[INCLUDES]
before = common.conf
[Definition]
_daemon = sshd
failregex = ^%(__prefix_line)s[iI](?:llegal|nvalid) user .*? from <HOST>(?: port \d+)?\s*$
ignoreregex =
[Init]
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd
Note : Parfois la regex ne matche pas, on pourra mettre une regex moins rigide :
puis on définit une jail ainsi :
[sshd-invaliduser]
enabled = true
maxretry = 1
logpath = %(sshd_log)s
banaction = %(banaction_allports)s
bantime = 12h
Note : Il est conseillé de mettre dans la configuration SSH
UseDNS no
.
Dovecot
La jail “dovecot” par défaut est désormais efficace (au moins à partir de Debian 12) :
[dovecot]
enabled = true
banaction = %(banaction_allports)s
maxretry = 3
findtime = 10m
bantime = 60m
Auparavant on ajoutait la règle
filter.d/dovecot-evolix.conf
:
[Definition]
failregex = (?: pop3-login|imap-login): .*(?:Authentication failure|Aborted login \(auth failed|Aborted login \(tried to use disabled|Disconnected \(auth failed|Aborted login \(\d+ authentication attempts).*rip=<HOST>,.*
ignoreregex =
puis on définit une jail ainsi :
[dovecot-evolix]
enabled = true
filter = dovecot-evolix
port = pop3,pop3s,imap,imaps,imap2,imap3,smtp,ssmtp
logpath = /var/log/mail.log
Voici un filtre pour matcher les erreurs SSL/TLS :
[Definition]
failregex = (?: pop3-login|imap-login): .*SSL_accept\(\) failed.*rip=<HOST>,.*
Courier
On utilise la règle courierlogin prédéfinie pour la jail :
[courierauth]
enabled = true
port = smtp,ssmtp,imap2,imap3,imaps,pop3,pop3s
filter = courierlogin
logpath = /var/log/mail.log
Postfix SASL
Le filtre Postfix étant complexe (cf ci-dessous), on préfère parfois
définir un nouveau filtre filter.d/evolix-sasl.conf
:
[Definition]
failregex = (?i): warning: [-._\w]+\[<HOST>\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed:
ignoreregex =
puis l’on définit une jail ainsi :
[sasl]
enabled = true
filter = evolix-sasl
logpath = /var/log/mail.log
banaction = %(banaction_allports)s
maxretry = 3
findtime = 10m
bantime = 60m
Postfix
Le filtre Postfix gère plusieurs modes : normal, rbl, more (normal+rbl), auth, ddos, extra ou aggressive (tous les modes).
Quelques exemples :
[postfix]
enabled = true
mode = auth
maxretry = 3
findtime = 10m
bantime = 60m
banaction = %(banaction_allports)s
[postfix-ddos]
enabled = true
filter = postfix[mode=ddos]
logpath = %(postfix_log)s
backend = %(postfix_backend)s
maxretry = 3
findtime = 10m
bantime = 60m
banaction = %(banaction_allports)s
[postfix-aggressive]
enabled = true
filter = postfix[mode=aggressive]
logpath = %(postfix_log)s
backend = %(postfix_backend)s
maxretry = 3
findtime = 10m
bantime = 60m
banaction = %(banaction_allports)s
Apache
Grâce aux journaux access_log et error_log générés par Apache, on peut utiliser Fail2Ban comme protection sur certaines URLs ou l’ensemble des requêtes. Cela peut être des règles activées en permanence ou des règles spécifiques pour répondre à une attaque DOS ou « brute force » en cours.
Attention : si il y a un reverse-proxy HTTP en amont, les règles seront inefficaces, il faudra mettre Fail2Ban sur le reverse-proxy.
Il existe des filtres prédéfinis pour Apache, le plus efficace est “apache-auth” que l’on pourra utiliser ainsi :
[apache-auth]
enabled = true
maxretry = 5
findtime = 10m
#banaction = dummy
banaction = %(banaction_allports)s
bantime = 1h
D’autres jails peuvent être intéressantes, même si il faudra se méfier des faux positifs :
[apache-badbots]
enabled = true
banaction = dummy
[apache-botsearch]
enabled = true
[apache-fakegooglebot]
enabled = true
[apache-modsecurity]
enabled = true
[apache-nohome]
enabled = true
[apache-noscript]
enabled = true
[apache-overflows]
enabled = true
[apache-shellshock]
enabled = true
Attention, assurez-vous d’avoir indiqué les bons chemins vers logs,
en particulier en ajustant apache_error_log
vua
/etc/fail2ban/paths-overrides.local
.
Limite globale
On peut définir des filtres assez simples (à adapter en fonction du type de logs, combined ou vhost_combined) :
# cat /etc/fail2ban/filter.d/apache-combined-all.conf
[Definition]
failregex = ^<HOST>
# cat /etc/fail2ban/filter.d/apache-combined-all-without3xx.conf
[Definition]
failregex = ^<HOST>
# cat /etc/fail2ban/filter.d/apache-vhost_combined-all.conf
[Definition]
failregex = ^.+:\d{1,5} <HOST>
# cat /etc/fail2ban/filter.d/apache-vhost_combined-post.conf
[Definition]
failregex = ^.+:\d{1,5} <HOST> -.*"POST
# cat /etc/fail2ban/filter.d/apache-vhost_combined-4xx.conf
[Definition]
failregex = ^.+:\d{1,5} <HOST> -.+".+" 4\d\d \d
# cat /etc/fail2ban/filter.d/apache-vhost_combined-post-4xx.conf
[Definition]
failregex = ^.+:\d{1,5} <HOST> -.+"POST .+" 4\d\d \d
Que l’on réutilisera avec précaution dans différentes jails.
Si l’on utilise mod_evasive avec les paramètres suivants :
DOSSiteCount 30
DOSSiteInterval 1
DOSBlockingPeriod 60
On pourra définir une jail pour avoir un comportement équivalent :
[apache-evasive-like]
enabled = true
filter = apache-vhost_combined-all
logpath = %(apache_access_log)s
maxretry = 30
findtime = 1
bantime = 60
#banaction = dummy
banaction = %(banaction_allports)s
On pourra aussi utiliser les logs de chaque site ainsi :
filter = apache-combined-all
logpath = /home/*/log/access.log
Évidemment, il faudra bien réfléchir en ajustant les valeurs maxretry/findtime, en fonction notamment du nombre total de requêtes pour charger une page si les contenus « statiques » ne sont pas séparés.
On pourra aussi envisager d’utiliser un log Apache où ne figureront pas certaines requêtes (URLs se terminant par js/css/jpg/png/ico/gif/etc. par exemple) :
SetEnvIf Request_URI "\.css$" fail2ban_ignore
SetEnvIf Request_URI "\.js$" fail2ban_ignore
SetEnvIf Request_URI "\.jpg$" fail2ban_ignore
SetEnvIf Request_URI "\.png$" fail2ban_ignore
SetEnvIf Request_URI "\.ico$" fail2ban_ignore
SetEnvIf Request_URI "\.gif$" fail2ban_ignore
SetEnvIf Request_URI "\.woff2$" fail2ban_ignore
SetEnvIf Request_URI "\.txt$" fail2ban_ignore
SetEnvIf Remote_Addr "127\.0\.0\.1" fail2ban_ignore
SetEnvIf Remote_Addr "::1" fail2ban_ignore
LogFormat "%h %t \"%r\" %v %>s" fail2ban
CustomLog /var/log/apache2/access-fail2ban.log fail2ban env=!fail2ban_ignore
avec une jail plus agressive du type :
[apache-evasive-like2]
enabled = true
filter = apache-combined-all-without3xx
logpath = /var/log/apache2/access-fail2ban.log
maxretry = 5
findtime = 3
bantime = 60
banaction = dummy
#banaction = %(banaction_allports)s
Voici d’autres exemples de valeurs maxretry/findtime :
# 50 reqs / 10s
maxretry = 50
findtime = 10
# 300 reqs / 10s
maxretry = 300
findtime = 10
Et voici d’autres exemples de jails :
[apache-post]
enabled = true
filter = apache-vhost_combined-post
logpath = %(apache_access_log)s
maxretry = 3
findtime = 10
[apache-4xx]
enabled = true
filter = apache-vhost_combined-4xx
logpath = %(apache_access_log)s
maxretry = 3
findtime = 10
[apache-post-4xx]
enabled = true
filter = apache-vhost_combined-post-4xx
logpath = %(apache_access_log)s
maxretry = 2
findtime = 30
Nginx
401
/etc/fail2ban/filter.d/nginx-401.conf :
Jail :
[nginx-401-allvhosts]
enabled = true
port = http,https
filter = nginx-401
logpath = /home/*/log/access.log tail
maxretry = 30
findtime = 3600
403
/etc/fail2ban/filter.d/nginx-403.conf :
Jail :
[nginx-403-allvhosts]
enabled = true
port = http,https
filter = nginx-403
logpath = /home/*/log/access.log tail
maxretry = 30
findtime = 3600
HAProxy
Exemple de filtres écrits manuellement :
# cat /etc/fail2ban/filter.d/haproxy-ssl-handshake.conf
[INCLUDES]
before = common.conf
[Definition]
_daemon = haproxy
failregex = ^.* <HOST>:\d{1,5} .*SSL handshake failure
# cat /etc/fail2ban/filter.d/haproxy-bad-host.conf
[INCLUDES]
before = common.conf
[Definition]
_daemon = haproxy
failregex = ^%(__prefix_line)s<HOST>.*<NOSRV> .* \{www\.example\.com/foo\}
exploités ainsi dans des jails :
[haproxy-bad-host]
enabled = true
logpath = /var/log/haproxy.log
maxretry = 1
findtime = 60
bantime = 86400
[haproxy-ssl-handshake]
enabled = false
logpath = /var/log/haproxy.log
maxretry = 5
findtime = 60
bantime = 600
webapps
Wordpress sans plugin
L’option la plus simple pour utiliser Fail2Ban avec Wordpress consiste à détecter un trop grand nombre de tentatives de login dans les journaux access_log.
On ajoute un filtre dans
/etc/fail2ban/filter.d/apache-wp.conf
:
puis on définit une jail du type
/etc/fail2ban/jail.d/apache-wp.conf
:
[apache-wp]
enabled = true
port = http,https
filter = apache-wp
logpath = /var/log/apache2/access.log
maxretry = 10
findtime = 300
Cette méthode a le défaut de ne pas distinguer les tentatives réussies et échouées, car la page d’authentification ne renvoie pas d’erreur HTTP particulière (Une proposition à cet effet a été faite, à suivre). Pour utiliser une méthode plus avancée (permettant notamment d’avoir un maxretry plus strict) voir ci-dessous.
Wordpress avec plugin simple
Une deuxième option est d’utiliser un plugin Wordpress pour envoyer
une erreur HTTP 401 en cas d’erreur d’authentification. Cette méthode
est plus fine mais nécessite de toucher à l’installation Wordpress : il
faut installer le plugin dans wp-content/mu-plugins,
un dossier spécial qui ne sera pas vu à travers l’interface
d’administration web. Il suffit de créer
wp-content/mu-plugins/401-on-login-fail.php
:
<?php
function my_login_failed_401() {
status_header( 401 );
}
add_action( 'wp_login_failed', 'my_login_failed_401' );
On ajoute ensuite un filtre dans
/etc/fail2ban/filter.d/apache-wp.conf
:
puis on définit une jail du type
/etc/fail2ban/jail.d/apache-wp.conf
:
[apache-wp]
enabled = true
port = http,https
filter = apache-wp
logpath = /var/log/apache2/access.log
/home/user/log/access.log
maxretry = 5
findtime = 300
Wordpress avec plugin Fail2Ban
La dernière solution utilise le plugin Wordpress fail2ban pour enregistrer les authentifications dans un fichier de log. Elle nécessite l’installation et la mise à jour régulière du plugin.
Le plugin est disponible sur https://wordpress.org/plugins/wp-fail2ban/.
On ajoute ensuite deux filtres dans
/etc/fail2ban/filter.d/wordpress-hard.conf
et
/etc/fail2ban/filter.d/wordpress-soft.conf
:
# Fail2Ban configuration file hard
#
# Author: Charles Lecklider
#
[INCLUDES]
# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf
[Definition]
_daemon = (?:wordpress|wp)
# Option: failregex
# Notes.: regex to match the password failures messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can
# be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
#
failregex = ^%(__prefix_line)sAuthentication attempt for unknown user .* from <HOST>( via XML-RPC)?$
^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>( via XML-RPC)?$
^%(__prefix_line)sBlocked user enumeration attempt from <HOST>$
^%(__prefix_line)sPingback error .* generated from <HOST>$
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =
# Fail2Ban configuration file soft
#
# Author: Charles Lecklider
#
[INCLUDES]
# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf
[Definition]
_daemon = (?:wordpress|wp)
# Option: failregex
# Notes.: regex to match the password failures messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can
# be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
#
failregex = ^%(__prefix_line)sAuthentication failure for .* from <HOST>$
^%(__prefix_line)sXML-RPC authentication failure from <HOST>$
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =
puis on définit deux jails du type :
[wordpress-hard]
enabled = true
port = http,https
filter = wordpress-hard
logpath = /var/log/auth.log
maxretry = 1
findtime = 300
[wordpress-soft]
enabled = true
port = http,https
filter = wordpress-soft
logpath = /var/log/auth.log
maxretry = 5
findtime = 300
ownCloud
Pour faire fonctionner ownCloud avec Fail2Ban, il faut tout d’abord
modifier le fichier config.php
pour enregistrer les
informations d’authentification :
'loglevel' => '2',
'log_authfailip' => true,
'logfile' => 'owncloud.log',
On ajoute un filtre dans
/etc/fail2ban/filter.d/owncloud.conf
:
[Definition]
failregex={"app":"core","message":"Login failed: user '.*' , wrong password, IP:<HOST>","level":2,"time":".*"}
puis on définit une jail du type
/etc/fail2ban/jail.d/owncloud.conf
:
[owncloud]
enabled = true
port = http,https
filter = owncloud
logpath = owncloud.log
maxrety = 5
findtime = 300
Joomla via les logs apache
On va simplement détecter un trop grand nombre de tentatives de login dans les journaux access_log.
On ajoute un filtre dans
/etc/fail2ban/filter.d/apache-joomla.conf
:
[Definition]
failregex = <HOST> -.*"POST.*/administrator/index.php HTTP
puis on définit une jail du type
/etc/fail2ban/jail.d/apache-joomla.conf
:
[apache-joomla]
enabled = true
port = http,https
filter = apache-joomla
logpath = /var/log/apache2/access.log
maxretry = 10
findtime = 300
Joomla via le log applicatif
On peut aussi utiliser les logs applicatifs de joomla et notamment, son log/error.php qui contient les erreurs de connexions.
Note: Ce log semble suivre la langue par défaut de l’installation. Il faut donc adapter ces regex
# Fail2Ban configuration file for joomla
[INCLUDES]
before = common.conf
[Definition]
failregex = ^.*INFO\s<HOST>\sjoomlafailure\sLe nom d\'utilisateur ne correspond pas au mot de passe, ou vous n\'avez pas encore de compte.$
ignoreregex =
puis, pour l’activer :
[joomla-monsite]
enabled = true
port = http,https
filter = joomla
logpath = /home/monsite/www/log/error.php
maxretry = 10
Prestashop
On va simplement détecter un trop grand nombre de tentatives de login dans les journaux access_log.
On ajoute un filtre dans
/etc/fail2ban/filter.d/apache-prestashop.conf
:
[Definition]
failregex = <HOST> -.*"POST.*/login.*
puis on définit une jail du type
/etc/fail2ban/jail.d/apache-prestashop.conf
:
[apache-prestashop]
enabled = true
port = http,https
filter = apache-prestashop
logpath = /var/log/apache2/access.log
maxretry = 10
findtime = 300
pm2 / nodejs
/etc/fail2ban/filter.d/pm2-auth-failure.conf :
[Definition]
failregex = .*Auth failure, WRONG_HASH for IP <HOST> .*
[Init]
datepattern = ^%%Y-%%m-%%d %%H:%%M
Définition de la jail :
[pm2-auth-failure]
enabled = true
port = http,https
filter = pm2-auth-failure
logpath = /home/APPPATH/.pm2/logs/api-error.log tail
maxretry = 30
findtime = 3600
haproxy-badbots
Inspiré du filtre de Debian apache-badbots.
/etc/fail2ban/filter.d/haproxy-badbots.conf :
[Definition]
badbotscustom = EmailCollector|WebEMailExtrac|TrackBack/1\.02|sogou music spider|ezooms.bot|BLEXBot|MJ12bot|AhrefsBot|Yandex|EvilRobot|360Spider|Yeti|Baiduspider
badbots = Atomic_Email_Hunter/4\.0|atSpider/1\.0|autoemailspider|bwh3_user_agent|China Local Browse 2\.6|ContactBot/0\.2|ContentSmartz|DataCha0s/2\.0|DBrowse 1\.4b|DBrowse 1\.4d|Demo Bot DOT 16b|Demo Bot Z 16b|DSurf15a 01|DSurf15a 71|DSurf15a 81|DSurf15a VA|EBrowse 1\.4b|Educate Search VxB|EmailSiphon|EmailSpider|EmailWolf 1\.00|ESurf15a 15|ExtractorPro|Franklin Locator 1\.8|FSurf15a 01|Full Web Bot 0416B|Full Web Bot 0516B|Full Web Bot 2816B|Guestbook Auto Submitter|Industry Program 1\.0\.x|ISC Systems iRc Search 2\.1|IUPUI Research Bot v 1\.9a|LARBIN-EXPERIMENTAL \(efp@gmx\.net\)|LetsCrawl\.com/1\.0 \+http\://letscrawl\.com/|Lincoln State Web Browser|LMQueueBot/0\.2|LWP\:\:Simple/5\.803|Mac Finder 1\.0\.xx|MFC Foundation Class Library 4\.0|Microsoft URL Control - 6\.00\.8xxx|Missauga Locate 1\.0\.0|Missigua Locator 1\.9|Missouri College Browse|Mizzu Labs 2\.2|Mo College 1\.9|MVAClient|Mozilla/2\.0 \(compatible; NEWT ActiveX; Win32\)|Mozilla/3\.0 \(compatible; Indy Library\)|Mozilla/3\.0 \(compatible; scan4mail \(advanced version\) http\://www\.peterspages\.net/?scan4mail\)|Mozilla/4\.0 \(compatible; Advanced Email Extractor v2\.xx\)|Mozilla/4\.0 \(compatible; Iplexx Spider/1\.0 http\://www\.iplexx\.at\)|Mozilla/4\.0 \(compatible; MSIE 5\.0; Windows NT; DigExt; DTS Agent|Mozilla/4\.0 efp@gmx\.net|Mozilla/5\.0 \(Version\: xxxx Type\:xx\)|NameOfAgent \(CMS Spider\)|NASA Search 1\.0|Nsauditor/1\.x|PBrowse 1\.4b|PEval 1\.4b|Poirot|Port Huron Labs|Production Bot 0116B|Production Bot 2016B|Production Bot DOT 3016B|Program Shareware 1\.0\.2|PSurf15a 11|PSurf15a 51|PSurf15a VA|psycheclone|RSurf15a 41|RSurf15a 51|RSurf15a 81|searchbot admin@google\.com|ShablastBot 1\.0|snap\.com beta crawler v0|Snapbot/1\.0|Snapbot/1\.0 \(Snap Shots, \+http\://www\.snap\.com\)|sogou develop spider|Sogou Orion spider/3\.0\(\+http\://www\.sogou\.com/docs/help/webmasters\.htm#07\)|sogou spider|Sogou web spider/3\.0\(\+http\://www\.sogou\.com/docs/help/webmasters\.htm#07\)|sohu agent|SSurf15a 11 |TSurf15a 11|Under the Rainbow 2\.2|User-Agent\: Mozilla/4\.0 \(compatible; MSIE 6\.0; Windows NT 5\.1\)|VadixBot|WebVulnCrawl\.unknown/1\.0 libwww-perl/5\.803|Wells Search II|WEP Search 00
failregex = ^.* <HOST>:\d{1,5} .*\{.*\|.*(?:%(badbots)s|%(badbotscustom)s).*\}.*$
ignoreregex =
Définition de la jail :
[haproxy-badbots]
enabled = true
port = http,https
filter = haproxy-badbots
logpath = /var/log/haproxy.log
maxretry = 10
bantime = 300
Keycloak
Inspiré du Gist https://gist.github.com/drmalex07/3eba8b98d0ac4a1e821e8e721b3e1816
/etc/fail2ban/filter.d/keycloak.conf :
[INCLUDES]
before = common.conf
[Definition]
_threadName = [a-z][-_0-9a-z]*(\s[a-z][-_0-9a-z]*)*
_userId = (null|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})
_realmName = (\S+)
_clientId = (\S+)
# preference 'user_not_found' as brute force is enabled for users with to may thumbs.
# 'user_not_found' user doesnt exist
# 'invalid_user_credentials' incorrect password
_error = (user_not_found|invalid_user_credentials)
failregex = ^.*WARN\s+\[org\.keycloak\.events\]\s+\(%(_threadName)s\)\s+type="LOGIN_ERROR",\srealmId="(.+)",\s+clientId="%(_clientId)s",\s+userId="%(_userId)s",\s+ipAddress="<HOST>",\s+error="%(_error)s"
ignoreregex =
Définition de la jail
[keycloak]
enabled = true
port = https,8443
logpath = /home/keycloak/keycloak.log
maxretry = 6
findtime = 600
bantime = 600
Munin
Pour activer les plugins Munin pour Fail2Ban :
# cd /etc/munin/plugins
# ln -s /usr/share/munin/plugins/fail2ban
FAQ
Fail2Ban supporte l’IPv6 ?
Le support de l’IPv6 est arrivé dans la version 0.10. Dans Debian, cette version est disponible qu’à partir de la version 10 (Buster) (et aussi dans les backports pour la version 9 (Stretch)
Attaque via un utilisateur
Un utilisateur local peut générer des logs vers syslog (par
exemple avec la commande logger), il faut donc bien
avoir en tête que toute jail s’appuyant sur des logs syslog
(comme /var/log/auth.log
) pourra être activée par un
utilisateur logué en SSH ou via PHP, CGI, etc.
Comment mettre en liste blanche certaines adresses IP ?
Pour mettre en whitelist certaines adresses IP, il faut utiliser le paramètre ignoreip qui peut s’utiliser globalement (section [DEFAULT]) ou au sein de chaque jail :
ignoreip = 127.0.0.1/8 192.0.2.42
Ou à chaud :
# fail2ban-client set sshd addignoreip 192.0.2.42
Comment bannir une ip manuellement ?
fail2ban-client set JAIL banip IP
Erreur “fail2ban.actions.action: ERROR” avec jail au nom trop long
Si le nom de votre jail dépasse 20 caractères, vous obtiendrez des erreurs du type :
fail2ban.actions.action: ERROR iptables -N fail2ban-accesslog-custom-12345
fail2ban.actions.action: ERROR iptables -n -L INPUT | grep -q fail2ban-accesslog-custom-12345 returned 100
la raison est que Fail2Ban utilise le nom de la jail pour ajouter une chaîne IPTables et elle ne doit pas dépasser une certaine longueur :
22:52 < reg> iptables v1.4.14: chain name `fail2ban-accesslog-custom-12345' too long (must be under 29 chars)
Taille de la base de données SQLite et durée de conservation des bans
Un
bug affecte les versions antérieures à 0.11 (donc jusqu’à Debian 10
inclus) dont la base /var/lib/fail2ban/fail2ban.sqlite3
n’est pas purgée.
Le contournement est de mettre en place un cron
/etc/cron.daily/fail2ban_dbpurge
:
#!/bin/sh
sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 ".timeout 5000; DELETE FROM bans WHERE datetime('now', '-86400 second') > datetime(timeofban, 'unixepoch'); VACUUM;"
Attention, cet exemple utilise 86.400 secondes (1 jour), ajustez
selon le paramètre dbpurgeage
dans
/etc/fail2ban/fail2ban.conf
.
Notez qu’avec ce contournement nous ne sommes pas (encore) 100% sûrs
que cela n’impacte pas les banissements très longs (on peut voir les
durées de banissements via
grep -R -E "bantime[[:blank:]]*=[[:blank:]]*[0-9]+" /etc/fail2ban/
)
ou les banissements illimités (bantime = -1
).
Si la base de données est volumineuse parce qu’elle n’a jamais été
purgée, la requête DELETE
peut échouer. Pour libérer de
l’espace, il faut faire des DELETE
successifs à la main
(qui peuvent prendre un certain temps) :
# sqlite3 /var/lib/fail2ban/fail2ban.sqlite3
sqlite > DELETE FROM bans WHERE date('now', '-1000 day') > datetime(timeofban, 'unixepoch');
sqlite > DELETE FROM bans WHERE date('now', '-500 day') > datetime(timeofban, 'unixepoch');
(...)
sqlite > DELETE FROM bans WHERE date('now', '-14 day') > datetime(timeofban, 'unixepoch');
sqlite > DELETE FROM bans WHERE date('now', '-86400 second') > datetime(timeofban, 'unixepoch');
sqlite > VACUUM;
Notez que la commande VACUUM
va faire une copie de la
base, il faut donc un minimum d’espace disponible.
ignorecommand
On peut utiliser ignorecommand
pour vérifier si une IP
est en liste blanche via une commande. Voici un exemple pour ignorer les
IPs des bot Google : https://forum.cleavr.io/t/ignore-google-bots-on-fail2ban/306
Google Bot et crawlers
Si l’on met des règles Fail2Ban pour le web, on peut vouloir mettre en liste blanche Google Bot & co. On peut connaître ses IPs via https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot?hl=fr
Google bot
https://developers.google.com/search/apis/ipranges/googlebot.json
En IPv4, cela donne à peu près cette liste un peu bourrine (novembre 2024) :
192.178.5.0/27 192.178.6.0/27 34.0.0.0/8 35.247.243.240/28 66.249.64.0/20
Google crawlers
https://developers.google.com/search/apis/ipranges/special-crawlers.json
En IPv4, cela donne à peu près cette liste un peu bourrine (novembre 2024) :
108.177.2.0/27 192.178.17.0/27 209.85.238.0/24 66.249.80.0/20 72.14.199.0/24 74.125.128.0/17
Google fetchers
a priori moins grave si ce sont ces « fetchers ».
https://developers.google.com/search/apis/ipranges/user-triggered-fetchers.json
https://developers.google.com/search/apis/ipranges/user-triggered-fetchers-google.json