001-linkedin-logotype-button 002-twitter-logo 003-social

Lorsque j'ai décidé de protéger mon projet Nounoumanager.com (sous Symfony 3) contre les tentatives de bruteforce, j'ai d'abord envisagé une solution propre à mon application (bundle ou développement maison). J'ai très rapidement changé d'avis et opté pour fail2ban qui excelle dans cet exercice. Grâce à cet utilitaire, on peut surveiller efficacement une application Symfony, un Piwik ou même PhpMyAdmin. Et c'est très simple à mettre en oeuvre !

Présentation de fail2ban

fail2ban analyse des logs en temps réel à la recherche d'activités suspectes et bloque les adresses IP coupables. Pour y parvenir, il utilise des fichiers de configurations appelés filtres, qui sont décrits par des regex dédiées à des formats de logs spécifiques. Son mode d'action en fait donc un outil très efficace pour lutter contre certaines attaques, comme les tentatives de DoS ou de bruteforce. Il est aussi très intéressant car il peut surveiller n'importe quelle application qui produit directement ou indirectement du log, comme une application Symfony, Piwik ou PhpMyAdmin...

Attention, être bloqué par fail2ban signifie que l'on se retrouve potentiellement privé de tout accès au serveur. Les faux-positifs peuvent donc avoir des conséquences fâcheuses pour un utilisateur légitime ou pour soit même !

Installation et configuration

apt-get install fail2ban

Le fichier jail.conf contient la configuration de référence utilisée par défaut par fail2ban. Il est très bien documenté. Ce fichier ne doit pas être modifié, on édite le fichier jail.local afin d'y définir nos propres règles de bloquage appelées des prisons. Nativement, fail2ban propose une cinquantaine de prisons adaptés à autant de services ou applications (ssh, apache, nginx, mysql, exim, dovecot, roundcube...). Voici par exemple une configuration qui fonctionne "Out of the box". Elle détermine des paramètres par défaut et active la surveillance des logs ssh et apache.

# /etc/fail2ban/jail.local

[DEFAULT]
# liste des IP qui ne seront jamais bannies (ah, un garde-fou contre les faux-positifs !)
ignoreip = 127.0.0.1, 192.168.1.50
# durée d'un ban, en secondes (soit 10mn ici)
bantime  = 600
# Une IP est bannie si "maxretry" actions suspectes sont trouvées durant les "findtime" dernières secondes
findtime = 600
maxretry = 3

# L'IP est bannie dès le premièr échec d'authentification SSH
[ssh]
enabled  = true
maxretry = 1    

# L'IP est banie après 3 échec d'authentification Apache
[apache]
enabled  = true
maxretry = 3

# ...

Pour prendre en compte une nouvelle configuration, on redémarre fail2ban

service fail2ban restart

Surveiller nos applications avec fail2ban

fail2ban permet de configurer des prisons personnalisées et les filtres qui vont avec. Pour cela, il suffit d'avoir du log permettant d'identifier les actions suspectes. Dans mon cas, je voulais détecter les tentatives de bruteforce, donc les tentatives d'authentification. La procédure est relativement simple :

  1. Créer un filtre à partir du pattern que l'on souhaite rechercher dans nos logs
  2. Créer une prison qui utilise ce filtre dans jail.local

Symfony

Par défaut sur Symfony 2 et 3, l'authentification est une requête POST à l'adresse /login_check. On retrouve effectivement cette requette dans les logs Apache :

192.168.1.23 - - [28/Feb/2017:21:29:36 +0100] "POST /login_check HTTP/1.1" 302 672 ...

Chaque reqûete à cette url est une tentative de connexion mais on ne distingue pas explicitement une tentative qui échoue. Néanmoins, on peut raisonnablement refuser un trop grand nombre de tentatives, qu'elles aboutissent ou non. On va donc définir un filtre dans le fichier filter.d/symfony-auth.conf

# /etc/fail2ban/filter.d/symfony-auth.conf
[Definition]
failregex = ^<HOST> .* "POST /login_check HTTP/1.1" 302
ignoreregex =

failregex sera recherché dans nos logs apache et toutes les lignes qui correspondent seront considérées comme des actions suspectes. Inversement, les lignes qui correspondent à ignoreregex seront ignorées. Il ne reste plus qu'à configurer la prison dans jail.local

# /etc/fail2ban/jail.local
[symfony-auth]
enabled  = true
port     = http,https
filter   = symfony-auth
logpath  = /var/log/apache*/symfony.access.log
maxretry = 10

On peut demander à fail2ban de ne bloquer que certains ports (http et https en l'occurence) et écraser la configuration par défaut (ici le nombre de tentatives maxretry). Avant de redémarrer fail2ban, on peut s'assurer que le nouveau filtre fonctionne. La commande suivante permet de compter le nombre de résultats qui correspondent

fail2ban-regex /var/log/apache2/symfony.access.log /etc/fail2ban/filter.d/symfony-auth.conf

Et le résultat devrait ressemble à ça

Results
=======

Failregex: 9 total
|-  #) [# of hits] regular expression
|   1) [9] ^<HOST> .* "POST /login_check HTTP/1.1" 302
`-

Tout fonctionne comme prévu ! On peut redémarrer fail2ban pour activer notre nouvelle prison

service fail2ban restart

Piwik

L'authentification de piwik se fait via une requête POST à la racine du hostname. C'est la seule requête POST envoyée à cette URL, on peut donc l'interpréter comme une tentative de connexion. Le filtre de fail2ban est encore plus simple que celui de Symfony

# /etc/fail2ban/filter.d/piwik-auth.conf
[Definition]
failregex = ^<HOST> .* "POST / HTTP/1.1" 200
ignoreregex =

On ajoute la prison

# /etc/fail2ban/jail.local
[piwik-auth]
enabled  = true
port     = http,https
filter   = piwik-auth
logpath  = /var/log/apache*/piwik.access.log
maxretry = 5

PhpMyAdmin

Phpmyadmin exporte les informations d'authentification vers l'environnement Apache. On peut donc configurer la directive LogFormat (dnas un virtualhost par exemple) afin qu'elle intègre ces informations.

LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %{userID}n %{userStatus}n" pma_combined

Les logs Apache peuvent être utilisés pour identifier les tentatives de connexion qui ont échouées, grâce au filtre suivant

# /etc/fail2ban/filter.d/phpmyadmin-auth.conf  
[Definition]
failregex = ^<HOST>.*(mysql-denied|allow-denied|root-denied|empty-denied)$
ignoreregex =

Il ne manque plus que l'activation de la prison.

# vi /etc/fail2ban/jail.local
[phpmyadmin-auth]
enabled  = true
port     = http,https
filter   = phpmyadmin-auth
logpath  = /var/log/apache*/phpmyadmin.access.log
maxretry = 3