Dans l’article précédent, je présentais le fonctionnement de fail2ban : Fail2ban : protéger son serveur des attaques DoS et Bruteforce.
Ce service scrute les logs et détecte certains comportement malveillants afin de bannir l’adresse IP.
Mais comment créer son propre filtre pour bloquer certaines attaques ou occurrences avec fail2ban ?
Dans cet article, voici toutes explications pour créer un filtre sur fail2ban et le mettre en place afin de protéger son serveur WEB des attaques par déni de service (DoS).
Fail2ban : créer un filtre pour bloquer les attaques DoS ou bruteforce
fail2ban fournit pas mal de prisons pour la plupart des services réseaux (HTTP, SMTP, SSH, etc).
Mais parfois, on peut être confronté à des situations qui nécessitent des actions, comportements ou attaques alors que les filtres ne sont pas disponibles.
Dans ces cas là, il faut créer son propre filtre fail2ban.
Pour cela, il faut maitriser un peu regex puisque c’est la base pour faire reconnaître un pattern à fail2ban.
Voici un serveur WEB (sous LighHTTP) avec pas mal de requêtes provenant d’IP dont le serveur WEB retourne un code HTTP 416.
On souhaite bannir les IP qui bouclent trop sur ce code car on estime qu’il y en a trop.
Voici un exemple, en XX.XX c’est l’IP du serveur WEB que j’ai masqué.
# 217.64.107.201 XX.XX.XX.XX - [10/Nov/2020:22:03:57 +0000] "GET /XXXXX
HTTP/1.1" 416 385 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.193 Safari/537.36"
Ce qui donne par exemple ceci dans les logs LighHTTP :
Pour y parvenir :
- il faudra créer un filtre qui permet de détecter ces connexions HTTP
- puis un fichier de prison pour configurer les actions à mener avec ses paramètres
Créer le filtre
On commence donc par créer le filtre suivant /etc/fail2ban/filter.d/lighttpd-406.conf :
[Definition]
datepattern = ^[^\[]*\[({DATE})
{^LN-BEG}
failregex = ^<HOST> XX\.XX\.XX\.XX - \[.*] "GET.*HTTP\/1.1" 416 385
ignoreregex =
- datepattern permet de configurer le foramt des dates utilisées dans les logs du serveur WEB
- ^<HOST> c’est l’IP que l’on souhaite bannir. Ce paramètre est obligatoire sinon le filtre ne fonctionnera pas
- puis le regex pour encadrer le contenu que l’on veut matcher
Si vous ne spécifiez pas HOST alors fail2ban retournera l’erreur suivant : ERROR: No failure-id group” from fail2ban-regex on custom filter.
Tester le filtre avec fail2ban-regex
Il est tout à fait possible de tester notre filtre avec une ligne d’exemple grâce à fail2ban-regex.
On lui donne la ligne à tester tout en spécifiant le filtre ou le fichier de logs, soit donc les syntaxes suivantes
fail2ban-regex 'le contenu a tester' /etc/fail2ban/filter.d/lighttpd-406.conf
ou via les logs :
fail2ban-regex /var/log/lighttpd/access.log /etc/fail2ban/filter.d/lighttpd-406.conf
Ce qui donne le test suivant :
fail2ban-regex '217.64.107.201 1.2.3.4 - [10/Nov/2020:22:03:57] "GET /XXX HTTP/1.1" 416 385 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.193 Safari/537.36' /etc/fail2ban/filter.d/lighttpd-406.conf
Lorsque le filtre fonctionne matched est à 1, sinon il est ignoré ou manqué (missed).
La portion qui ne match pas est alors indiquée pour correction.
Ajustez votre filtre jusqu’à ce qu’il matche le contenu à bannir.
Créer la prison
Une fois que votre filtre fonctionne, il ne reste plus qu’à configurer la prison.
Pour cela, on créé le fichier de configuration de la prison avec sa zone.
Il se trouve dans /etc/fail2ban/jail.d/lighttp.conf avec ce contenu qui reprend l’exemple précédent.
[lighttpd-406]
enabled = true
filter = lighttpd-406
action = iptables-ipset-proto4[name=blacklist, port="http,https", protocol=tcp, bantime=0]
logpath = /var/log/lighttpd/access.log
findtime = 600
bantime = 7200
maxretry = 4
On utilise donc le filtre lighttpd-406 et un bannissement par Ipset.
Le reste a déjà évoqué.
Enfin il ne reste plus qu’à relancer le service fail2ban :
/etc/init.d/fail2ban reload
Quand cela fonctionne fail2ban détecte et ban les IP.
Ce qui permet de faire tomber le nombre de requêtes par secondes sur le serveur WEB.
Ces graphiques munin le confirment après mise en place.
Enfin le graph munin de la prison fail2ban :
Créer un filtre pour bloquer une attaque DoS L7
Voici un autre exemple avec une attaque DoS Layer 7 sur un serveur WEB.
On peut créer une règle pour gérer celle-ci.
L’étude de l’attaque DoS
Comprenez qu’une attaque DoS, c’est un envoie de commandes à un botnet.
En clair donc tous reçoivent le même ordre et la même action est menée.
Elle peut-être plus ou moins sophistiquée mais en général, la demande de ressources à la page est assez identique.
On peut donc dégager un pattern qui permet de reconnaître l’attaque d’une requête WEB saine.
A partir de là, il est possible de créer une règle fail2ban pour bloquer et bannir les IP.
Ici, on voit que :
- C’est toujours la même page WEB qui est demanée
- Le user-agent est aléatoire
- le référer est aléatoire mais pris dans une liste 4/5 sites (facebook, qq, google, youtube).
On ne peut pas bloquer les requêtes depuis ces referer car sinon on bloquerait aussi du trafic légitime.
Maintenant, est-ce normal d’avoir plusieurs requêtes WEB d’une IP avec une referer ?
La réponse est non.
En général, c’est plutôt, je viens de Google, Youtube ou d’un site WEB puis je navigue dans le site (et donc plus de referer ou lui même).
C’est plus une excuse ici pour montrer jusqu’où on peut aller avec les filtres fail2ban.
Créer le filtre fail2ban en réponse
Ainsi, il est tout à fait possible de créer un filtre qui bloque trop de requêtes en un espace de temps limité avec un referer.
Il faut :
- Bloquer toutes les requêtes avec un referer SAUF notre site où aucun referer
- Viser que les pages PHP ou qui se terminent par /. C’est le cas des pages WordPress de mon site : https://www.malekal.com/super-article-de-ouf/
- Il ne faut pas que ce soit des images.. en effet, quand on parcoure un google images, on peut avoir plusieurs requêtes d’affilés avec Google. Cela bloquerait du trafic légitime
- On peut exclure certains domaines en referer.
Voici à quoi cela peut ressembler.
En haut on a en commentaire des requêtes illégitimes issues de l’attaques DoS.
#109.105.205.186 - - [13/Nov/2020:13:44:34 +0100] "GET /viewtopic.php?f=2&t=67342 HTTP/1.1" 403 571 "https://www.qq.com" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
#109.238.208.130 - - [13/Nov/2020:13:44:35 +0100] "GET /viewtopic.php?f=2&t=67342 HTTP/1.1" 403 169 "https://www.reddit.com" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Mobile/15E148 Safari/604.1"
[Definition]
datepattern = ^[^\[]*\[({DATE})
{^LN-BEG}
failregex = ^<HOST> - - \[.*] "(GET|POST|HEAD) \/[a-z0-9_-]+(\.php|\/)(\?[a-z0-9=&]+)? HTTP\/[0-9]\.[0-9]" [0-9]{3} [0-9]{1,5} "https?:\/\/(?![a-z]+\.(malekal|commentcamarche[supprimer-trojan)\.(com|net))
ignoreregex =
Les explications du failregex.
- On récupère l’HOST du client
- puis on encadre la date de la requête avec [.*]… on pourrait ici améliorer le regex pour le réduire
- Ensuite la méthode HTTP
- puis la page demandée, là ça se complexifie un peu.
- \/[a-z0-9_-]+(.php|\/) : n’importe quelle page PHP ou qui se termine par /. Cela exclut ainsi toutes les images .jpg, .png, etc.
- (\?[a-z0-9=&]+)? : puis on autorise les paramètres de la page PHP souvent ?parametres=valeur si présent avec le ? à la fin car ce n’est pas obligatoire
- Puis on encadre la version HTTP : HTTP\/[0-9].[0-9]”
- Le code HTTP et la taille de la page [0-9]{3} [0-9]{1,5}
- Enfin le referer :
- “https?:\/\/ ce sera donc une page sécurisée. Cela exclut les requêtes avec un referer vide
- (?![a-z]+.(malekal|commentcamarche|supprimer-trojan).(com|net)) permet d’exclure notre site et commentcamarche.
Il ne reste plus qu’à tester les combinaisons avec fail2ban-regex.
On voit bien que cela répond à nos attentes.
Tester le filtre fail2ban fasse à une attaque DoS
Il ne reste plus qu’à tester en pratique.
Pour cela, j’ai fait une vidéo.
Le filtre fail2ban maison fonctionne à la perfection.
Mais ici le problème est que l’action est CloudFlare.
En effet, on peut utiliser l’API pour les bannir sur CloudFlare mais cet envoie se fait par curl et c’est très lent.
Ainsi pour bannir l’intégralité des attaques, soit environ 400 IP, il a fallu plusieurs minutes.
Ce n’est donc pas très réactif.
Par contre avec une action de type ipset, cela doit permettre de bloquer l’attaque par deni de service.
Fail2ban a banni toutes les IP attaquantes, un peu de plus de 400.
On les retrouve bien aussi sur CloudFlare.
Du coup, si l’attaque se reproduit, CloudFlare l’a bloquera en amont du serveur WEB.
Bon bien sûr, c’est un exemple de ce que l’on peut faire avec fail2ban.
Pour une attaque DoS plus conséquente ou plus sophistiqués, cela ne fonctionnera pas.