ModSecurity z zestawem reguł OWASP CRS włącza się w dziesięć minut, a wyłącza trzy dni później — po tym, jak artykuły przestają się zapisywać w panelu, psuje się wysyłanie plików, a klient nie może złożyć zamówienia, bo w adresie znalazł się apostrof. Wniosek „WAF przeszkadza w pracy” nasuwa się sam i jest błędny: niemal wszystkie te blokady da się usunąć trzema–czterema precyzyjnymi wyjątkami, a cała rzecz polega na tym, żeby je poprawnie znaleźć.

Nie włączać blokowania od razu

Pierwszy tydzień to sama obserwacja. W /etc/modsecurity/modsecurity.conf:

SecRuleEngine DetectionOnly

W tym trybie WAF zapisuje wszystko, co by zablokował, i nie blokuje niczego. Tydzień prawdziwego ruchu — łącznie z własną pracą w panelu, wysyłaniem obrazków i złożeniem zamówienia — daje listę rzeczywistych fałszywych trafień zamiast hipotetycznych. Przejście na On ma sens dopiero wtedy, gdy ta lista zostanie przerobiona.

Jak działa CRS: nie jedna reguła, lecz suma

To klucz do wszystkiego dalszego. CRS prawie nigdy nie blokuje żądania jedną regułą. Każda reguła, która zadziała, dodaje punkty anomalii, a blokada następuje, gdy suma przekroczy próg. Dlatego w dzienniku widać nie jeden wiersz, lecz kilka, a ostatnim będzie reguła o identyfikatorze 949110 — ta, która podsumowuje.

Praktyczny wniosek: wyjątek trzeba zrobić dla reguły, która naliczyła punkty, a nie dla 949110. Wyłączając regułę sumującą, wyłącza się cały zestaw, a z WAF-a zostaje tylko wiersz w pliku konfiguracyjnym.

Drugi wniosek dotyczy poziomu paranoi. Domyślnie wynosi jeden i to właściwy wybór. Poziomy 2 i 3 dodają reguły, które z założenia dają fałszywe trafienia na zwykłych witrynach; włączać je warto dopiero wtedy, gdy poziom pierwszy jest w pełni dostrojony.

Znaleźć winną regułę

Wszystko potrzebne jest w dzienniku audytu (/var/log/modsec_audit.log) i w dzienniku błędów serwera WWW. Szukamy po czasie blokady:

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

To lista częstotliwości reguł, które zadziałały. Dalej, dla konkretnego identyfikatora, sprawdzamy, na czym dokładnie zadziałała:

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

Potrzebne są trzy rzeczy: identyfikator reguły, nazwa parametru (ARGS:content, ARGS:comment) i ścieżka żądania. Z nich buduje się wyjątek.

Zwykli podejrzani

Lista powtarza się od witryny do witryny:

  • 942100 — wstrzyknięcie SQL. Reaguje na pola tekstowe o długiej treści: treść artykułu, opis produktu, komentarz. Cudzysłowy, nawiasy i słowa w rodzaju select wyglądają dla detektora podejrzanie nawet w zwykłej prozie;
  • 941100 i rodzina 941xxx — XSS. Przychodzą razem z edytorem wizualnym: znaczniki HTML w polu to właśnie sens jego pracy;
  • 920420 — niedozwolony Content-Type. Psuje interfejsy API i wysyłanie plików: domyślny zestaw dozwolonych typów jest wąski, a w starszych wersjach nie było w nim application/json;
  • 913100 — skaner po nagłówku User-Agent. Razem ze skanerami łapie legalne narzędzia: monitoring dostępności, curl we własnych skryptach;
  • 200002, 200004 — błędy analizy treści żądania. Zwykle oznaczają nie atak, lecz przekroczony limit rozmiaru, czyli wysyłanie dużego pliku.

Trzy sposoby na wyjątek

Według rosnącej brutalności. Wszystkie trafiają do własnego pliku (na przykład /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES.conf), a nie do plików samego CRS: zestaw reguł jest aktualizowany i zmiany zniknęłyby razem z nim.

Wyjąć jeden parametr spod jednej reguły. Najdokładniejszy wariant i ten, do którego dążymy:

SecRuleUpdateTargetById 942100 "!ARGS:content"

Wyłączyć regułę tylko na jednej ścieżce. Pasuje, gdy hałasuje konkretna strona — edytor, import, formularz opinii:

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

Wyłączyć regułę całkowicie. Ostateczność i prawie zawsze znak, że przyczyny nie znaleziono:

SecRuleRemoveById 942100

Różnica między pierwszym a trzecim jest istotna. W pierwszym przypadku jedno pole — tekst artykułu — przestaje być sprawdzane pod kątem wstrzyknięcia SQL przez jedną regułę; w drugim i trzecim przestaje być sprawdzana cała witryna. Różnica w nakładzie pracy to jakieś pięć minut.

Po zmianach sprawdzamy konfigurację i przeładowujemy łagodnie:

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

Kolejność, która oszczędza tydzień

  1. Tydzień w DetectionOnly ze zwykłą pracą na witrynie, łącznie z panelem i wysyłaniem plików.
  2. Lista częstotliwości reguł z dziennika audytu. Przerabiać od góry — pierwsze trzy–cztery dadzą dziewięćdziesiąt procent szumu.
  3. Przy każdej: ustalić, jaki parametr i na jakiej stronie. Wyjątek robi się według parametru, a nie według reguły.
  4. Dopiero teraz SecRuleEngine On.
  5. Raz w miesiącu zajrzeć do blokad: witryna się zmieniła, więc pojawiły się nowe fałszywe trafienia.

Właśnie na tym ostatnim punkcie wszystko zwykle się rozbija: dziennik audytu to gigabajty tekstu, których nikt nie będzie czytał ręcznie. Sens zebranego panelu polega na tym, żeby lista reguł, które zadziałały, zablokowane żądania i aktywny zestaw były przed oczami, a nie wydobywane grepem. Jak to wygląda, pokazuje strona demonstracyjna poniżej.