Mise sur liste noire dynamique des adresses IP avec NGINX Plus et fail2ban

NGINX | 19 septembre 2017

Photo : Arnold Reinhold – Travail personnel, CC BY‑SA 3.0

Vous ne vous en rendez peut-être pas compte, mais votre site web est constamment menacé. S’il fonctionne sous WordPress, des robots tentent de vous envoyer du spam. S’il comporte une page de connexion, il est la cible d’attaques par force brute visant les mots de passe. Vous pouvez également considérer les robots d’indexation des moteurs de recherche comme des visiteurs indésirables.

Protéger votre site contre les activités indésirables, suspectes et malveillantes n’est pas une mince affaire. Les pare-feu d’applications web, tels que NGINX App Protect et les modules certifiés NGINX Plus proposés par nos partenaires, sont des outils efficaces qui doivent faire partie intégrante de votre arsenal de sécurité. Dans la plupart des environnements, on n’est jamais trop prudent en matière de sécurité, et une approche multicouche est toujours la plus efficace.

Dans cet article de blog, nous aborderons l’utilisation de fail2ban comme couche supplémentaire de la pile de sécurité web. Fail2ban est un système de détection d’intrusion (IDS) qui surveille en permanence les fichiers journaux afin de repérer toute activité suspecte, puis exécute une ou plusieurs actions préconfigurées. En règle générale, fail2ban détecte les tentatives de connexion infructueuses, puis bloque (bannit) l’adresse IP responsable pendant une durée déterminée. Il s’agit d’une protection simple, mais efficace contre les attaques par force brute visant les mots de passe.

Dans NGINX Plus R13, nous avons introduit un magasin clé-valeur natif et une nouvelle API NGINX Plus. Cela ouvre la voie à une multitude de solutions de configuration dynamique permettant à un système externe de piloter la configuration et le comportement de NGINX Plus. Dans cet article de blog, nous montrerons également comment utiliser fail2ban pour reconfigurer automatiquement NGINX Plus afin d’ignorer les requêtes provenant d’adresses IP à l’origine de plusieurs échecs d’authentification.

Note de l’éditeur – À partir de NGINX Plus R16, les magasins clé-valeur peuvent être synchronisés entre toutes les instances NGINX Plus d’un cluster. (Le partage d’état au sein d’un cluster est également disponible pour d’autres fonctionnalités de NGINX Plus.) Pour plus de détails, consultez notre blog et le guide d’administration de NGINX Plus.

Utilisation d’un magasin clé-valeur pour la mise sur liste de refus d’adresses IP

Le magasin clé-valeur de NGINX Plus est un magasin natif en mémoire qui présente trois caractéristiques principales :

  1. Les paires clé-valeur sont représentées sous forme d’objets JSON
  2. Les paires clé-valeur sont entièrement gérées via une API
  3. Les valeurs sont accessibles à NGINX Plus sous forme de variables de configuration standard

Le magasin clé-valeur est défini en créant une zone de mémoire partagée nommée par la directive keyval_zone. Ce magasin clé-valeur peut ensuite être alimenté avec un ensemble initial de valeurs en envoyant un objet JSON à l’API à l’aide de la méthode HTTP POST. La directive keyval définit ensuite la variable existante qui sera utilisée comme clé de recherche ($remote_addr dans l’exemple), ainsi que le nom d’une nouvelle variable ($num_failures) dont la valeur sera déterminée à partir de la valeur correspondant à cette clé.

Configuration et gestion du magasin clé-valeur de NGINX Plus

L’API est activée en désignant un bloc « location » comme point de terminaison de l’API NGINX Plus.


server {
    listen 1111;
    allow  127.0.0.1; # Only allow access from localhost,
    deny   all;       # and prevent remote access.

    location /api {
        api write=on; # The NGINX Plus API endpoint in read/write mode
    }
}
keyval_zone zone=denylist:1M;
keyval $remote_addr $num_failures zone=denylist;

server {
    listen 80;

    location / {
        root /usr/share/nginx/html;
        if ($num_failures) {
            return 403;
        }
    }
}

vim: syntax=nginx

Avant que nous y ajoutions des paires clé-valeur, une requête portant sur le contenu du magasin denylist renvoie un objet JSON vide.

$ curl http://localhost:1111/api/6/http/keyvals/denylist{}

Le magasin clé-valeur peut désormais être initialisé avec une première paire clé-valeur à l’aide de la méthode HTTP « POST » (sous la forme de l’argument « -d » de la commande « curl ») afin d’envoyer un nouvel objet JSON. Plusieurs paires clé-valeur peuvent être envoyées par requête « POST » à un magasin clé-valeur vide, puis uniquement une par une par la suite.

$ curl -iX POST -d '{"10.0.0.1":"1"}' http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 201 Created
...

Une paire clé-valeur est supprimée en envoyant une requête PATCH sur la clé avec la valeur null.

$ curl -iX PATCH -d '{"10.0.0.1":null}' http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 204 No Content
...

Toutes les paires clé-valeur peuvent être supprimées du magasin clé-valeur en envoyant la méthode « DELETE ».

$ curl -iX DELETE http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 204 No Content
...

Une implémentation simple de la mise sur liste de refus d’adresses IP peut désormais être configurée.


server {
    listen 1111;
    allow  127.0.0.1; # Only allow access from localhost,
    deny   all;       # and prevent remote access.

    location /api {
        api write=on; # The NGINX Plus API endpoint in read/write mode
    }
}
keyval_zone zone=denylist:1M;
keyval $remote_addr $num_failures zone=denylist;

server {
    listen 80;

    location / {
        root /usr/share/nginx/html;
        if ($num_failures) {
            return 403;
        }
    }
}

vim: syntax=nginx

Cet extrait de configuration configure le port 80 en tant que serveur web. La ligne 18 affecte à la variable « $num_failures » la valeur de la paire clé-valeur de la zone de mémoire partagée « denylist » dont la clé correspond à la variable « $remote_addr » (adresse IP du client). Le processus d’évaluation de la variable « $num_failures » peut être décrit plus clairement en plusieurs étapes :

  1. Transmettre la valeur de « $remote_addr » au magasin clé-valeur « denylist »
  2. Si $remote_addr correspond exactement à une clé, obtenez la valeur de cette paire clé-valeur
  3. Renvoyez la valeur sous la forme $num_failures

Ainsi, si l’adresse IP du client a été envoyée via une requête POST au magasin clé-valeur, toutes les requêtes entraînent alors une erreur HTTP 403 « Forbidden ». L’avantage de cette approche, par rapport à l’utilisation d’un bloc map aux mêmes fins, est que la liste de blocage des adresses IP peut être contrôlée par un système externe.

Utiliser fail2ban pour gérer dynamiquement la liste de blocage des adresses IP

Comme indiqué précédemment, fail2ban est couramment utilisé pour détecter les activités suspectes et/ou malveillantes dans les fichiers journaux, puis prendre des mesures afin de protéger le système. L’action par défaut consiste à configurer iptables pour rejeter tous les paquets provenant de l’adresse IP consignée dans les journaux. Bien qu’efficace pour bloquer un acteur malveillant, cette approche offre une très mauvaise expérience aux utilisateurs légitimes, en particulier s’ils ont oublié leur mot de passe. Pour eux, le site web semble tout simplement indisponible.

La configuration suivante décrit une action fail2ban qui envoie l’adresse IP incriminée au magasin clé-valeur de liste de blocage de NGINX Plus. NGINX Plus affiche alors une page d’erreur plus utile et applique simultanément une limite de débit des requêtes à cette adresse IP, de sorte que, si ces actions font partie d’une attaque, celle-ci est efficacement neutralisée.

Nous supposons que fail2ban est installé avec la configuration par défaut sur le même hôte que NGINX Plus, et que tous les fichiers de configuration se trouvent dans le répertoire /etc/fail2ban.

L’action fail2ban suivante utilise l’API NGINX Plus pour ajouter et supprimer les adresses IP « bannies » dans le magasin clé-valeur denylist, de la même manière que dans notre exemple simple ci-dessus. Nous plaçons le fichier nginx-plus-denylist.conf dans le répertoire /etc/fail2ban/action.d.


[Definition]
actionban = curl -s -o /dev/null -d '{"":""}' http://localhost:1111/api/6/http/keyvals/denylist
actionunban = curl -s -o /dev/null -X PATCH -d '{"":null}' http://localhost:1111/api/6/http/keyvals/denylist

La jail fail2ban suivante active le filtre intégré qui détecte les tentatives de connexion infructueuses à l’aide du module d’authentification HTTP Basic de NGINX et applique l’action définie dans nginx-plus-denylist.conf. De nombreux autres filtres intégrés sont disponibles, et il est facile d’en créer pour détecter les activités indésirables dans les journaux d’accès de NGINX.


[DEFAULT]
bantime   = 120
banaction = nginx-plus-denylist
 
[nginx-http-auth]
enabled = true

L’extrait de configuration NGINX Plus suivant applique l’authentification HTTP Basic à la page « Welcome to NGINX » par défaut. Nous plaçons le fichier password_site.conf dans le répertoire /etc/nginx/conf.d.


server {
    listen 1111;
    allow  127.0.0.1; # Only allow access from localhost,
    deny   all;       # and prevent remote access.

    location /api {
        api write=on; # The NGINX Plus API endpoint in read/write mode
    }
}

keyval_zone zone=denylist:1M state=denylist.json;
keyval $remote_addr $num_failures zone=denylist;

limit_req_zone $binary_remote_addr zone=20permin:10M rate=20r/m;

server {
    listen 80;
    root /usr/share/nginx/html;

    location / {
        auth_basic "closed site";
        auth_basic_user_file users.htpasswd;

        if ($num_failures) {
            rewrite ^.* /banned.html;
        }
    }

    location = /banned.html {
        limit_req zone=20permin burst=100;
    }
}

vim: syntax=nginx

La ligne 11 définit la zone keyval_zone comme dans le fichier denylist_keyval.conf ci-dessus, mais avec l’ajout du paramètre state, qui spécifie un fichier dans lequel l’état du magasin clé-valeur est enregistré afin qu’il soit conservé lorsque NGINX Plus est arrêté puis redémarré. Le magasin clé-valeur est conservé lors des rechargements ordinaires de la configuration sans qu’il soit nécessaire de spécifier un fichier d’état. (Les lignes 1 à 10 ne sont pas affichées, mais sont identiques à celles du fichier denylist_keyval.conf.)

La ligne 14 crée une zone de mémoire partagée appelée « 20permin » et définit un débit maximal de 20 requêtes par minute pour chaque adresse IP cliente. Cette limite de débit est ensuite appliquée lorsqu’une adresse IP est ajoutée à la liste de blocage par fail2ban (ligne 30).

Le bloc server définit un serveur web qui écoute sur le port par défaut (80), où toutes les requêtes sont traitées par le bloc location / (ligne 20). La directive root (ligne 18) indique l’emplacement de notre contenu web. Les lignes 21 et 22 précisent que l’authentification HTTP Basic est requise pour ce site et que la base de données contenant les mots de passe des utilisateurs autorisés se trouve dans le fichier users.htpasswd. La documentation de la directive auth_basic_user_file décrit le format de ce fichier. Notez que cette configuration n’est pas chiffrée à des fins de test et que tout site web en production utilisant l’authentification HTTP Basic doit utiliser SSL/TLS pour sécuriser le transport.

Lorsque l’adresse IP du client a été placée sur liste noire (lignes 24 à 26), au lieu de renvoyer le code d’erreur HTTP 403, nous réécrivons la requête pour qu’elle pointe vers /banned.html, qui est ensuite traitée dans le bloc location correspondant exactement à cet URI (ligne 29). Une page web statique expliquant que l’adresse IP de l’utilisateur a été bloquée en raison d’un nombre excessif d’échecs de connexion est alors renvoyée. Une limitation stricte du débit est également appliquée (ligne 30) afin d’empêcher un client malveillant de consommer inutilement les ressources système.

Page affichée aux utilisateurs lorsque leur adresse IP est placée sur liste de blocage

(Le code HTML de cet exemple de page web est disponible dans le fichier « banned.html » du dépôt GitHub associé à cet article.)

Nous pouvons simuler une série de tentatives de connexion infructueuses et l’activité correspondante de fail2ban comme suit.

$ curl -i http://admin:password@www.example.com/HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:admin@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:pass1234@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:letmein@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:fred@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl http://admin:P@ssw0rd@www.example.com/
<!DOCTYPE html>
<html>
<head>
<title>Banned</title>
...
$ tail -f /var/log/fail2ban.log
2017-09-15 13:55:18,903 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:55:28,836 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:49,228 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:50,286 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,015 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,088 fail2ban.actions  [28498]: NOTICE  [nginx-http-auth] Ban 172.16.52.1
2017-09-15 13:59:52,379 fail2ban.actions  [28498]: NOTICE  [nginx-http-auth] Unban 172.16.52.1

Cette solution de mise sur liste de blocage dynamique des adresses IP peut désormais fonctionner sans aucune autre modification de la configuration. Fail2ban surveille les fichiers journaux de NGINX et ajoute les adresses IP bannies au magasin clé-valeur de NGINX Plus à l’aide de l’API. Après 120 secondes (la durée bantime configurée dans jail.local), l’adresse IP concernée est retirée de la liste de blocage, là encore à l’aide de l’API NGINX Plus, et les tentatives de connexion provenant de cette adresse sont de nouveau acceptées.

L’API et le magasin clé-valeur de NGINX Plus rendent ce type d’intégration possible. Il est désormais possible de mettre en œuvre des solutions de configuration avancées sans avoir à créer de fichiers de configuration NGINX Plus ni à effectuer de rechargements. Nous aimerions savoir comment vous utilisez ces fonctionnalités pour créer vos propres solutions de configuration dynamique. N’hésitez pas à laisser un commentaire ci-dessous.

Pour essayer NGINX Plus, commencez votre essai gratuit de 30 jours dès aujourd’hui ou contactez-nous pour discuter de vos cas d’utilisation.


Partager

À propos de l’auteur

Liam Crilly
Liam CrillyDirecteur senior de la gestion des produits

Autres articles de Liam Crilly

Articles de blog associés

Tendances actuelles des technologies cloud natives, de l’ingénierie de plateforme et de l’IA
NGINX | 13/10/2025

Tendances actuelles des technologies cloud natives, de l’ingénierie de plateforme et de l’IA

Découvrez les principaux enseignements tirés des réponses à l’enquête annuelle F5 NGINX sur les dernières tendances en matière de technologies cloud natives, de sécurité, d’ingénierie de plateforme et d’IA.

Quels contrôleurs Ingress NGINX sont affectés par les vulnérabilités CVE-2022-4886, CVE-2023-5043 et CVE-2023-5044 ?
NGINX | 03/11/2023

Quels contrôleurs Ingress NGINX sont affectés par les vulnérabilités CVE-2022-4886, CVE-2023-5043 et CVE-2023-5044 ?

Lorsque de nouvelles CVE sont signalées, il est facile de ne plus savoir si elles concernent un outil de contrôleur Ingress NGINX particulier, car il existe plusieurs projets. Cet article présente les CVE et explique comment distinguer les différents projets de contrôleurs Ingress afin de dissiper toute confusion.

Votre API est-elle en temps réel ? Testez sa latence avec l’outil rtapi de NGINX
NGINX | 11/03/2020

Votre API est-elle en temps réel ? Testez sa latence avec l’outil rtapi de NGINX

Votre API répond-elle aux requêtes en moins de 30 ms ? Si ce n’est pas le cas, il ne s’agit pas d’une API en temps réel offrant la faible latence exigée par la grande majorité des organisations. Mesurez la latence de votre API avec l’outil rtapi de NGINX et découvrez comment NGINX peut l’aider à offrir une réactivité en temps réel.

Un testeur d’expressions régulières pour NGINX et NGINX Plus
NGINX | 11/06/2019

Un testeur d’expressions régulières pour NGINX et NGINX Plus

Le testeur d’expressions régulières (regex) pour NGINX et NGINX Plus vous évite de tâtonner en vous indiquant si une expression régulière utilisée dans un bloc location ou map correspond aux valeurs comme prévu. Il génère la configuration appropriée et affiche les valeurs des groupes de capture.

Caches partagés avec les clusters de cache NGINX Plus, partie 1
NGINX | 24/01/2017

Caches partagés avec les clusters de cache NGINX Plus, partie 1

Optimisez la capacité de votre cache web en le répartissant entre plusieurs serveurs de cache web NGINX ou NGINX Plus. Découvrez comment procéder dans cet article.

Introduction aux microservices
NGINX | 19/05/2015

Introduction aux microservices

Les microservices suscitent actuellement beaucoup d’intérêt. Cet article de blog est le premier d’une série de sept consacrée à la conception, au développement et au déploiement de microservices.