ModSecurity mit dem Regelsatz OWASP CRS ist in zehn Minuten eingeschaltet und drei Tage später wieder aus — nachdem sich Artikel im Verwaltungsbereich nicht mehr speichern lassen, der Datei-Upload nicht mehr funktioniert und ein Kunde keine Bestellung aufgeben kann, weil in seiner Adresse ein Apostroph vorkam. Der Schluss „die WAF stört den Betrieb“ liegt nahe, und er ist falsch: Fast alle diese Blockaden lassen sich mit drei oder vier gezielten Ausnahmen beheben, und der ganze Trick besteht darin, sie richtig zu finden.

Schalten Sie das Blockieren nicht sofort ein

Die erste Woche gilt nur der Beobachtung. In /etc/modsecurity/modsecurity.conf:

SecRuleEngine DetectionOnly

In diesem Modus protokolliert die WAF alles, was sie blockiert hätte, und blockiert nichts. Eine Woche echter Datenverkehr — einschließlich Ihrer eigenen Arbeit im Verwaltungsbereich, des Bilder-Uploads und einer Bestellung — ergibt eine Liste tatsächlicher Fehlalarme statt hypothetischer. Auf On umzustellen ist erst sinnvoll, wenn diese Liste abgearbeitet ist.

Wie CRS arbeitet: nicht eine Regel, sondern eine Summe

Das ist der Schlüssel zu allem Weiteren. CRS blockiert eine Anfrage fast nie mit einer einzigen Regel. Jede ausgelöste Regel addiert Anomaliepunkte, und die Blockade erfolgt, wenn die Summe einen Schwellenwert überschreitet. Deshalb sehen Sie im Protokoll nicht eine Zeile, sondern mehrere — und die letzte davon ist die Regel mit der Kennung 949110, jene, die die Summe zieht.

Die praktische Folge: Die Ausnahme muss für die Regel gelten, die Punkte vergeben hat, nicht für 949110. Schalten Sie die summierende Regel ab, schalten Sie den gesamten Satz ab, und die WAF bleibt als Zeile in einer Konfigurationsdatei übrig.

Die zweite Folge betrifft die Paranoia-Stufe. Sie steht standardmäßig auf eins, und das ist die richtige Wahl. Die Stufen 2 und 3 bringen Regeln mit, die auf gewöhnlichen Websites konstruktionsbedingt Fehlalarme erzeugen; sie einzuschalten lohnt erst, wenn Stufe eins vollständig eingestellt ist.

Die schuldige Regel finden

Alles Nötige steht im Audit-Log (/var/log/modsec_audit.log) und im Fehlerprotokoll des Webservers. Suchen Sie nach dem Zeitpunkt der Blockade:

sudo grep -o 'id "[0-9]*"' /var/log/modsec_audit.log | sort | uniq -c | sort -rn | head

Das ist eine Häufigkeitsliste der ausgelösten Regeln. Danach sehen Sie zu einer bestimmten Kennung nach, woran genau sie ausgelöst hat:

sudo grep -A5 'id "942100"' /var/log/modsec_audit.log | head -40

Drei Dinge werden gebraucht: die Regelkennung, der Parametername (ARGS:content, ARGS:comment) und der Anfragepfad. Daraus wird die Ausnahme gebaut.

Die üblichen Verdächtigen

Die Liste wiederholt sich von Website zu Website:

  • 942100 — SQL-Injection. Löst bei Textfeldern mit langem Inhalt aus: Artikeltext, Produktbeschreibung, Kommentar. Anführungszeichen, Klammern und Wörter wie select wirken auf den Detektor auch in normaler Prosa verdächtig;
  • 941100 und die Familie 941xxx — XSS. Sie kommen mit dem visuellen Editor: HTML-Tags in einem Feld sind genau der Sinn seiner Arbeit;
  • 920420 — unzulässiger Content-Type. Bricht APIs und Datei-Uploads: Der Standardsatz erlaubter Typen ist eng, und in älteren Versionen fehlte application/json darin;
  • 913100 — Scanner anhand des User-Agent. Fängt neben Scannern auch legitime Werkzeuge: Verfügbarkeitsüberwachung, curl in Ihren eigenen Skripten;
  • 200002, 200004 — Fehler beim Zerlegen des Anfragekörpers. Sie bedeuten meist keinen Angriff, sondern eine überschrittene Größenbeschränkung, also einen großen Datei-Upload.

Drei Wege, eine Ausnahme zu setzen

Nach zunehmender Grobheit. Alle gehören in eine eigene Datei (etwa /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES.conf) und nicht in die CRS-Dateien selbst: Der Regelsatz wird aktualisiert, und Ihre Änderungen verschwänden mit ihm.

Einen Parameter aus einer Regel herausnehmen. Die genaueste Variante und das Ziel:

SecRuleUpdateTargetById 942100 "!ARGS:content"

Eine Regel nur auf einem Pfad abschalten. Passend, wenn eine bestimmte Seite laut ist — ein Editor, ein Import, ein Bewertungsformular:

SecRule REQUEST_URI "@beginsWith /admin/post" \
    "id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=942100"

Die Regel vollständig abschalten. Letztes Mittel und fast immer ein Zeichen dafür, dass die Ursache nie gefunden wurde:

SecRuleRemoveById 942100

Der Unterschied zwischen dem ersten und dem dritten Weg ist erheblich. Im ersten Fall wird ein Feld — der Artikeltext — von einer Regel nicht mehr auf SQL-Injection geprüft; im zweiten und dritten wird die gesamte Website nicht mehr geprüft. Der Unterschied im Aufwand beträgt etwa fünf Minuten.

Nach den Änderungen die Konfiguration prüfen und sanft neu laden:

sudo apachectl configtest && sudo systemctl reload apache2
sudo nginx -t && sudo systemctl reload nginx

Eine Reihenfolge, die eine Woche spart

  1. Eine Woche DetectionOnly mit normaler Arbeit auf der Website, Verwaltungsbereich und Uploads eingeschlossen.
  2. Häufigkeitsliste der Regeln aus dem Audit-Log. Von oben nach unten abarbeiten — die ersten drei bis vier machen neunzig Prozent des Lärms aus.
  3. Zu jeder: klären, welcher Parameter auf welcher Seite. Die Ausnahme nach Parameter setzen, nicht nach Regel.
  4. Erst jetzt SecRuleEngine On.
  5. Einmal im Monat in die Blockaden schauen: Die Website hat sich verändert, also sind neue Fehlalarme hinzugekommen.

Am letzten Punkt scheitert es üblicherweise: Das Audit-Log ist gigabyteweise Text, den niemand von Hand liest. Der Sinn eines zusammengeführten Panels besteht darin, die Liste der ausgelösten Regeln, die blockierten Anfragen und den aktiven Regelsatz vor Augen zu haben, statt sie mit grep herauszuholen. Wie das aussieht, zeigt die Demo-Seite unten.