Login Logout

Howto Fail2Ban

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-server -V
Fail2Ban v0.8.13

Copyright (c) 2004-2008 Cyril Jaquier, 2008- Fail2Ban Contributors
Copyright of modifications held by their respective authors.
Licensed under the GNU General Public License v2 (GPL).

Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>.
Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>.

Configuration

Fichiers de configuration :

/etc/fail2ban
├── fail2ban.conf
├── jail.conf
├── jail.local
├── action.d
│   ├── apf.conf
│   ├── badips.conf
│   ├── blocklist_de.conf
│   […]
├── fail2ban.d
├── filter.d
│   ├── 3proxy.conf
│   ├── apache-auth.conf
│   ├── apache-badbots.conf
│   […]
└── jail.d

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 ssh (activée par défaut à l’installation) :

[ssh]                        

enabled = true               
port    = ssh                
filter  = sshd               
logpath  = /var/log/auth.log
maxretry = 6                 

Cette jail va surveiller le fichier /var/log/auth.log via la règle de filtrage /etc/fail2ban/filter.d/sshd.conf : s’il détecte la correspondance 6 fois en 10 minutes (temps par défaut), 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 port concerné pendant 10 minutes.

Les paramètres par défaut sont :

maxretry = 3
findtime = 600
banaction = iptables-multiport
bantime = 600
ignoreip = 127.0.0.1/8

Les paramètres par défaut et un certain nombre de jails (non activées à part ssh) 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.

Pour dumper la configuration courante :

# fail2ban-client -d
['set', 'loglevel', 3]
['set', 'logtarget', '/var/log/fail2ban.log']
[...]

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

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

fail2ban-client

De nombreuses commandes sont possibles avec fail2ban-client pour lister des informations ou modifier des paramètres.

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:
[...]

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 :

[Definition]
failregex =  warning: \[<HOST>\]: authentication failed:
ignoreregex =

On peut tester sa règle avec la commande fail2ban-regex en précisant 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:
[...]

$ fail2ban-regex auth.log /etc/fail2ban/filter.d/evolix-test.conf
[...]

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, l’action sera par défaut iptables-multiport (définie dans jail.conf) :

banaction = iptables-multiport

Exemple pour bannir et déclencher l’envoi d’un email :

action = iptables-multiport
         sendmail[dest=jdoe@example.com]

Exemples

Dovecot

On ajoute la règle filter.d/evolix-dovecot.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=(?P<host>\S*),.*
ignoreregex =

puis on définit une jail ainsi :

[dovecot]
enabled = true
filter = evolix-dovecot
port = pop3,pop3s,imap,imaps
logpath = /var/log/mail.log

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

La règle SASL ne convient pas, il faut la modifier via une nouvelle règle 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
port     = smtp,ssmtp,submissions,imap2,imap3,imaps,pop3,pop3s
filter   = evolix-sasl
logpath  = /var/log/mail.log

Apache / Nginx

Grâce aux journaux access_log générés par Apache / Nginx, 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.

Voici un exemple avec un filtre /etc/fail2ban/filter.d/dos-http.conf qui va contrôler l’ensemble des requêtes avec la méthode HTTP GET :

[Definition]
failregex = ^ -.*GET

puis on définit une jail du type :

[dos-http]
enabled = true
port = http,https
filter = dos-http
logpath = access.log
maxretry = 300
findtime = 300

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 :

[Definition]
failregex = <HOST> -.*"POST.*/wp-login.php HTTP.* 200
            <HOST> -.*"POST.*/xmlrpc.php.*

puis on définit une jail du type :

[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:

[Definition]
  failregex = <HOST>.*POST.*(wp-login\.php|xmlrpc\.php).* 401

puis on définit une jail du type :

[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 et /etc/fail2ban/filter.d/wordpress-soft :

# 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
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 :

[owncloud]
enabled = true
port    = http,https
filter  = owncloud  
logpath = owncloud.log
maxrety = 5
findtime = 300

Joomla

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.*

puis on définit une jail du type :

[apache-joomla]
enabled  = true
port     = http,https
filter   = apache-joomla
logpath  = /var/log/apache2/access.log
maxretry = 10
findtime = 300

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 :

[apache-prestashop]
enabled  = true
port     = http,https
filter   = apache-prestashop
logpath  = /var/log/apache2/access.log
maxretry = 10
findtime = 300

Munin

Pour activer les plugins Munin pour Fail2Ban :

# cd /etc/munin/plugins
# ln -s /usr/share/munin/plugins/fail2ban 

FAQ

Fail2Ban supporte l’IPv6 ?

Non, Fail2Ban ne supporte pas encore l’IPv6, c’est prévu pour la version 0.10 (encore expérimentale).

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

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)