Dynamische IP-Sperrlisten mit NGINX Plus und fail2ban

NGINX | 19. September 2017

Foto: Arnold Reinhold – Eigenes Werk, CC BY‑SA 3.0

Vielleicht ist Ihnen das nicht bewusst, aber Ihre Website ist ständig Bedrohungen ausgesetzt. Wenn sie mit WordPress betrieben wird, versuchen Bots, sie mit Spam zu überfluten. Wenn sie über eine Anmeldeseite verfügt, kommt es zu Brute-Force-Angriffen auf Passwörter. Auch Suchmaschinen-Crawler können Sie als unerwünschte Besucher betrachten.

Ihre Website vor unerwünschten, verdächtigen und böswilligen Aktivitäten zu schützen, ist keine leichte Aufgabe. Web Application Firewalls wie NGINX App Protect und die von unseren Partnern angebotenen NGINX Plus Certified Modules sind wirksame Tools und sollten als Teil Ihrer Sicherheitsarchitektur berücksichtigt werden. In den meisten Umgebungen kann es kaum zu viel Sicherheit geben, und ein mehrschichtiger Ansatz ist stets am effektivsten.

In diesem Blogbeitrag erläutern wir den Einsatz von fail2ban als zusätzliche Ebene des Web-Sicherheitskonzepts. Fail2ban ist ein Intrusion-Detection-System (IDS), das Protokolldateien kontinuierlich auf verdächtige Aktivitäten überwacht und anschließend eine oder mehrere vorkonfigurierte Maßnahmen ergreift. In der Regel überwacht fail2ban fehlgeschlagene Anmeldeversuche und blockiert (sperrt) daraufhin die betreffende IP-Adresse für einen bestimmten Zeitraum. Dies ist ein einfacher, aber wirksamer Schutz vor Brute-Force-Passwortangriffen.

Mit NGINX Plus R13 haben wir einen nativen Schlüssel-Wert-Speicher und eine neue NGINX Plus-API eingeführt. Dies ermöglicht eine Vielzahl dynamischer Konfigurationslösungen, bei denen die Konfiguration und das Verhalten von NGINX Plus durch ein externes System gesteuert werden können. In diesem Blogbeitrag zeigen wir außerdem, wie sich NGINX Plus mithilfe von fail2ban automatisch so neu konfigurieren lässt, dass Anfragen von IP-Adressen ignoriert werden, die mehrere fehlgeschlagene Authentifizierungsversuche verursacht haben.

Anmerkung der Redaktion – Ab NGINX Plus R16 können Schlüssel-Wert-Speicher über alle NGINX Plus-Instanzen in einem Cluster hinweg synchronisiert werden. (Die gemeinsame Nutzung von Zustandsinformationen in einem Cluster ist auch für andere Funktionen von NGINX Plus verfügbar.) Weitere Informationen finden Sie in unserem Blogbeitrag und im Administratorhandbuch zu NGINX Plus.

Verwenden eines Schlüssel-Wert-Speichers für IP-Sperrlisten

Der Schlüssel-Wert-Speicher von NGINX Plus ist ein nativer In-Memory-Speicher mit drei wesentlichen Merkmalen:

  1. Schlüssel-Wert-Paare werden als JSON-Objekte dargestellt
  2. Schlüssel-Wert-Paare werden vollständig über eine API verwaltet
  3. Die Werte stehen NGINX Plus als reguläre Konfigurationsvariablen zur Verfügung

Der Schlüssel-Wert-Speicher wird durch Erstellen einer gemeinsam genutzten Speicherzone definiert, deren Name mit der Direktive keyval_zone festgelegt wird. Anschließend kann dieser Schlüssel-Wert-Speicher mit einem anfänglichen Satz von Werten befüllt werden, indem mithilfe der HTTP-Methode POST ein JSON-Objekt an die API übermittelt wird. Die Direktive keyval legt dann fest, welche vorhandene Variable als Suchschlüssel verwendet wird (im Beispiel $remote_addr) und wie die neue Variable heißt ($num_failures), deren Wert anhand des zugehörigen Werts dieses Schlüssels ermittelt wird.

Konfiguration und Verwaltung des Schlüssel-Wert-Speichers von NGINX Plus

Die API wird aktiviert, indem ein location-Block als NGINX Plus-API-Endpunkt festgelegt wird.


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

Bevor wir Schlüssel-Wert-Paare hinzufügen, gibt eine Abfrage des Inhalts des denylist-Speichers ein leeres JSON-Objekt zurück.

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

Der Schlüssel-Wert-Speicher kann nun mit einem ersten Schlüssel-Wert-Paar befüllt werden, indem über die HTTP-POST-Methode (in Form des Arguments -d für den Befehl curl) ein neues JSON-Objekt übermittelt wird. Mehrere Schlüssel-Wert-Paare können per POST an einen leeren Schlüssel-Wert-Speicher gesendet werden, danach jedoch nur noch einzeln.

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

Ein Schlüssel-Wert-Paar wird entfernt, indem dem Schlüssel mit PATCH der Wert null zugewiesen wird.

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

Alle Schlüssel-Wert-Paare können aus dem Schlüssel-Wert-Speicher entfernt werden, indem die Methode DELETE gesendet wird.

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

Eine einfache Implementierung von IP-Sperrlisten kann jetzt konfiguriert werden.


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

Dieser Konfigurationsausschnitt konfiguriert Port 80 als Webserver. In Zeile 18 wird für die Variable $num_failures der Wert des Schlüssel-Wert-Paars in der Shared-Memory-Zone denylist ermittelt, dessen Schlüssel dem Wert der Variable $remote_addr (IP-Adresse des Clients) entspricht. Dieser Vorgang zur Ermittlung von $num_failures lässt sich schrittweise verständlicher darstellen:

  1. Übergeben Sie den Wert von „$remote_addr“ an den Schlüssel-Wert-Speicher „denylist“.
  2. Wenn $remote_addr genau mit einem Schlüssel übereinstimmt, rufen Sie den Wert dieses Schlüssel-Wert-Paars ab
  3. Geben Sie den Wert als $num_failures zurück

Wenn also die IP-Adresse des Clients per POST an den Schlüssel-Wert-Speicher übermittelt wurde, führen alle Anfragen zu einem HTTP-Fehler 403 Forbidden. Der Vorteil dieses Ansatzes gegenüber der Verwendung eines map-Blocks für denselben Zweck besteht darin, dass die IP-Sperrliste von einem externen System gesteuert werden kann.

Mit fail2ban die IP-Sperrliste dynamisch verwalten

Wie bereits erwähnt, wird fail2ban häufig eingesetzt, um verdächtige und/oder bösartige Aktivitäten in Protokolldateien zu erkennen und anschließend Maßnahmen zum Schutz des Systems zu ergreifen. Standardmäßig wird iptables so konfiguriert, dass alle Pakete verworfen werden, die von der protokollierten IP-Adresse stammen. Dieser Ansatz blockiert böswillige Akteure zwar wirksam, führt jedoch bei legitimen Benutzern zu einer äußerst schlechten Benutzererfahrung – insbesondere, wenn sie ihr Passwort vergessen haben. Für diese Benutzer sieht es so aus, als wäre die Website schlicht nicht erreichbar.

Die folgende Konfiguration beschreibt eine fail2ban-Aktion zur Übermittlung der betreffenden IP-Adresse an den Schlüssel-Wert-Speicher für die Sperrliste von NGINX Plus. NGINX Plus zeigt daraufhin eine hilfreichere Fehlerseite an und begrenzt gleichzeitig die Anfragerate für diese IP-Adresse, sodass ein etwaiger Angriff wirksam neutralisiert wird.

Wir gehen von einer Standardinstallation von fail2ban auf demselben Host wie NGINX Plus aus, wobei sich die gesamte Konfiguration im Verzeichnis /etc/fail2ban befindet.

Die folgende fail2ban-Aktion verwendet die NGINX Plus-API, um „gesperrte“ IP-Adressen im Key-Value-Speicher denylist hinzuzufügen und zu entfernen, genau wie in unserem einfachen Beispiel oben. Wir legen die Datei nginx-plus-denylist.conf im Verzeichnis /etc/fail2ban/action.d ab.


[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

Das folgende fail2ban-Jail aktiviert den integrierten Filter, der fehlgeschlagene Anmeldeversuche über das HTTP-Basic-Authentication-Modul von NGINX erkennt und die in nginx-plus-denylist.conf definierte Aktion ausführt. Es stehen viele weitere integrierte Filter zur Verfügung. Zudem lassen sich problemlos eigene Filter erstellen, um unerwünschte Aktivitäten in den NGINX-Zugriffsprotokollen zu erkennen.


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

Das folgende NGINX Plus-Konfigurationsfragment wendet die HTTP-Basic-Authentifizierung auf die standardmäßige Seite „Welcome to NGINX“ an. Wir legen die Datei password_site.conf im Verzeichnis /etc/nginx/conf.d ab.


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

Zeile 11 definiert die keyval_zone wie oben in denylist_keyval.conf, jedoch ergänzt um den Parameter state, der eine Datei angibt, in der der Zustand des Schlüssel-Wert-Speichers gespeichert wird, sodass er beim Beenden und Neustarten von NGINX Plus erhalten bleibt. Bei regulären Konfigurationsneuladungen bleibt der Schlüssel-Wert-Speicher auch ohne Angabe einer Zustandsdatei erhalten. (Die Zeilen 1–10 sind nicht dargestellt, entsprechen jedoch denen in denylist_keyval.conf.)

Zeile 14 erstellt eine Shared-Memory-Zone namens „20permin“ und legt für jede Client-IP-Adresse eine maximale Anfragerate von 20 Anfragen pro Minute fest. Diese Ratenbegrenzung wird angewendet, sobald fail2ban eine IP-Adresse zur Sperrliste hinzufügt (Zeile 30).

Der server-Block definiert einen Webserver, der den Standardport (80) überwacht und bei dem alle Anfragen vom location /-Block verarbeitet werden (Zeile 20). Die root-Direktive (Zeile 18) gibt an, wo sich unsere Webinhalte befinden. Die Zeilen 21–22 legen fest, dass für diese Website eine HTTP-Basisauthentifizierung erforderlich ist und dass sich die Passwortdatenbank für autorisierte Benutzer in der Datei users.htpasswd befindet. Die Dokumentation zur auth_basic_user_file-Direktive beschreibt das Format dieser Datei. Beachten Sie, dass diese Konfiguration zu Testzwecken unverschlüsselt ist und jede produktive Website, die eine HTTP-Basisauthentifizierung verwendet, SSL/TLS für die Transportsicherheit einsetzen sollte.

Wenn die IP-Adresse des Clients auf die Sperrliste gesetzt wurde (Zeilen 24–26), schreiben wir die Anfrage um, anstatt den HTTP-Fehlercode 403 zurückzugeben. Das Ziel der umgeschriebenen Anfrage ist /banned.html, die anschließend im location-Block verarbeitet wird, der exakt diesem URI entspricht (Zeile 29). Dadurch wird eine statische Webseite zurückgegeben, die erläutert, dass die IP-Adresse des Benutzers aufgrund zu vieler fehlgeschlagener Anmeldeversuche gesperrt wurde. Außerdem wird eine strenge Ratenbegrenzung angewendet (Zeile 30), die verhindert, dass ein böswilliger Client unnötig Systemressourcen beansprucht.

Seite, die Benutzern angezeigt wird, wenn ihre IP-Adresse auf der Sperrliste steht

(Der HTML-Code für diese Beispielwebseite ist als „banned.html“ im GitHub-Repository zu diesem Beitrag verfügbar.)

Wir können aufeinanderfolgende fehlgeschlagene Anmeldeversuche und die entsprechende Aktivität von fail2ban wie folgt simulieren.

$ 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

Diese Lösung für dynamische IP-Sperrlisten kann nun ohne weitere Konfigurationsänderungen ausgeführt werden. Fail2ban überwacht die NGINX-Protokolldateien und fügt gesperrte IP-Adressen mithilfe der API zum Schlüssel-Wert-Speicher von NGINX Plus hinzu. Nach 120 Sekunden (der in bantime konfigurierten Sperrdauer in jail.local) wird die betreffende IP-Adresse erneut mithilfe der NGINX Plus API von der Sperrliste entfernt, und Anmeldeversuche von dieser Adresse werden wieder akzeptiert.

Die NGINX Plus-API und der Schlüssel-Wert-Speicher ermöglichen diese Art der Integration. Fortschrittliche Konfigurationslösungen lassen sich nun umsetzen, ohne NGINX Plus-Konfigurationsdateien erstellen und die Konfiguration neu laden zu müssen. Wir würden gerne erfahren, wie Sie diese Funktionen nutzen, um eigene dynamische Konfigurationslösungen zu entwickeln. Hinterlassen Sie unten einen Kommentar.

Wenn Sie NGINX Plus ausprobieren möchten, starten Sie noch heute Ihre kostenlose 30-Tage-Testversion oder kontaktieren Sie uns, um Ihre Anwendungsfälle zu besprechen.


Teilen

Über den Autor

Liam Crilly
Liam CrillySenior Director, Produktmanagement

Weitere Blogbeiträge von Liam Crilly

Ähnliche Blogbeiträge

Die Zukunft von NGINX: Zurück zu unseren Open-Source-Wurzeln
NGINX | 23.08.2022

Die Zukunft von NGINX: Zurück zu unseren Open-Source-Wurzeln

Beim NGINX Sprint 2022 besinnen wir uns auf unsere Open-Source-Wurzeln. Erfahren Sie mehr über die Initiativen, die wir im kommenden Jahr verfolgen, und teilen Sie uns mit, was Sie sich wünschen.

Ein Leitfaden zur Auswahl eines Ingress-Controllers, Teil 1: Ermitteln Sie Ihre Anforderungen
NGINX | 08.09.2021

Ein Leitfaden zur Auswahl eines Ingress-Controllers, Teil 1: Ermitteln Sie Ihre Anforderungen

Ein Ingress-Controller kann eines der leistungsstärksten Tools in Ihrem Kubernetes-Stack sein. Erfahren Sie, wie Sie Ihre Ingress-Anforderungen ermitteln, um die beste Option auszuwählen.

Bereitstellung des NGINX Ingress Controllers auf Amazon EKS: So haben wir getestet
NGINX | 16.08.2021

Bereitstellung des NGINX Ingress Controllers auf Amazon EKS: So haben wir getestet

Wir haben kürzlich den Lösungsleitfaden zum NGINX Ingress Controller um Empfehlungen zur Dimensionierung für Amazon Elastic Kubernetes Service (EKS) ergänzt. In diesem Blogbeitrag erläutern wir, wie wir diese Empfehlungen erarbeitet haben, und stellen Ihnen alle Informationen zur Verfügung, die Sie benötigen, um selbst ähnliche Tests durchzuführen.

Protokollierung des Datenverkehrs zwischen NGINX und Upstream-Servern bei CDN77
NGINX | 12.12.2018

Protokollierung des Datenverkehrs zwischen NGINX und Upstream-Servern bei CDN77

Bei der Fehlersuche bei Datenverkehrsproblemen zwischen NGINX und Upstream-Servern konnte CDN77 die benötigten Daten nur aus dem Fehlerprotokoll mit aktivierter Debug-Ausgabe beziehen, was die Leistung beeinträchtigt und riesige Dateien erzeugt. Daher entwickelte das Team ein neues benutzerdefiniertes Modul, das ausschließlich die benötigten Daten protokolliert.

Ankündigung von NGINX Plus R16
NGINX | 05.09.2018

Ankündigung von NGINX Plus R16

NGINX Plus R16 ist eine einheitliche, elastische Ingress- und Egress-Schicht für Anwendungen, die die Funktionen eines Load Balancers, API-Gateways und einer WAF vereint und neue Features wie clusterfähige Ratenbegrenzung, Schlüssel-Wert-Speicher, „Random with Two Choices“, verbessertes UDP-Load-Balancing und vieles mehr bietet.

5 Gründe für den Umstieg auf softwarebasiertes Load Balancing
NGINX | 13.10.2016

5 Gründe für den Umstieg auf softwarebasiertes Load Balancing

Entdecken Sie fünf Gründe für den Wechsel zu einem softwarebasierten Load Balancer wie NGINX Plus, darunter Kosteneinsparungen, die Einführung von DevOps und eine einheitliche Lösung für alle Umgebungen.