ModSecurity con l’insieme di regole OWASP CRS si attiva in dieci minuti e si disattiva tre giorni dopo, quando gli articoli smettono di salvarsi nell’area amministrativa, il caricamento dei file si rompe e un cliente non riesce a inviare un ordine perché nel suo indirizzo c’era un apostrofo. La conclusione «il WAF ostacola il lavoro» viene da sé, ed è sbagliata: quasi tutti quei blocchi si risolvono con tre o quattro eccezioni precise, e tutto sta nel trovarle correttamente.

Non attivi subito il blocco

La prima settimana è di sola osservazione. In /etc/modsecurity/modsecurity.conf:

SecRuleEngine DetectionOnly

In questa modalità il WAF registra tutto ciò che avrebbe bloccato e non blocca nulla. Una settimana di traffico reale — compreso il suo lavoro nell’area amministrativa, il caricamento di immagini e un ordine — dà l’elenco dei falsi positivi veri invece che ipotetici. Passare a On ha senso solo quando quell’elenco è stato smaltito.

Come funziona il CRS: non una regola, ma una somma

È la chiave di tutto ciò che segue. Il CRS non blocca quasi mai una richiesta con una sola regola. Ogni regola che scatta aggiunge punti di anomalia, e il blocco avviene quando la somma supera una soglia. Per questo nel registro non vedrà una riga ma diverse, e l’ultima sarà la regola con identificatore 949110, quella che tira le somme.

Conseguenza pratica: l’eccezione va fatta per la regola che ha assegnato i punti, non per la 949110. Disattivando la regola che somma, disattiva l’intero insieme, e del WAF resta soltanto una riga in un file di configurazione.

La seconda conseguenza è il livello di paranoia. È a uno per impostazione predefinita, ed è la scelta giusta. I livelli 2 e 3 aggiungono regole che per costruzione producono falsi positivi su siti ordinari; attivarli ha senso solo quando il livello uno è del tutto messo a punto.

Trovare la regola colpevole

Tutto il necessario sta nel registro di audit (/var/log/modsec_audit.log) e nel registro degli errori del server web. Cerchi per ora del blocco:

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

È un elenco di frequenza delle regole scattate. Poi, per un identificatore preciso, guardi su che cosa esattamente è scattata:

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

Servono tre cose: l’identificatore della regola, il nome del parametro (ARGS:content, ARGS:comment) e il percorso della richiesta. Da questi si costruisce l’eccezione.

I soliti sospetti

L’elenco si ripete da un sito all’altro:

  • 942100: SQL injection. Scatta su campi di testo con contenuto lungo: corpo di un articolo, descrizione di un prodotto, commento. Virgolette, parentesi e parole come select appaiono sospette al rilevatore anche in prosa normale;
  • 941100 e la famiglia 941xxx: XSS. Arrivano insieme all’editor visuale: i tag HTML in un campo sono proprio il senso del suo lavoro;
  • 920420: Content-Type non consentito. Rompe le API e i caricamenti: l’insieme dei tipi ammessi di default è ristretto, e nelle versioni vecchie application/json non c’era;
  • 913100: scanner in base allo User-Agent. Insieme agli scanner cattura strumenti legittimi: il monitoraggio della disponibilità, curl nei suoi stessi script;
  • 200002, 200004: errori nell’analisi del corpo della richiesta. Di solito non indicano un attacco ma un limite di dimensione superato, cioè il caricamento di un file grande.

Tre modi di creare un’eccezione

In ordine di brutalità crescente. Tutti vanno in un file proprio (per esempio /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES.conf) e non nei file del CRS: l’insieme di regole viene aggiornato e le sue modifiche sparirebbero con esso.

Togliere un parametro da una regola. La variante più precisa, e quella a cui puntare:

SecRuleUpdateTargetById 942100 "!ARGS:content"

Disattivare una regola solo su un percorso. Va bene quando è una pagina precisa a fare rumore: un editor, un’importazione, un modulo di recensione:

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

Disattivare del tutto la regola. Ultima risorsa e quasi sempre segno che la causa non è stata trovata:

SecRuleRemoveById 942100

La differenza fra la prima e la terza è notevole. Nel primo caso un campo — il testo dell’articolo — smette di essere controllato per SQL injection da una regola; nel secondo e nel terzo smette di essere controllato l’intero sito. La differenza di fatica è di circa cinque minuti.

Dopo le modifiche, verifichi la configurazione e ricarichi con delicatezza:

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

Un ordine che fa risparmiare una settimana

  1. Una settimana in DetectionOnly con lavoro normale sul sito, area amministrativa e caricamenti compresi.
  2. Elenco di frequenza delle regole dal registro di audit. Si affronta dall’alto in basso: le prime tre o quattro fanno il novanta per cento del rumore.
  3. Per ciascuna: capire quale parametro e su quale pagina. L’eccezione si fa per parametro, non per regola.
  4. Solo adesso SecRuleEngine On.
  5. Una volta al mese, dare un’occhiata ai blocchi: il sito è cambiato, quindi sono comparsi nuovi falsi positivi.

È su quest’ultimo punto che di solito tutto si rompe: il registro di audit sono gigabyte di testo che nessuno leggerà a mano. Il senso di un pannello è avere davanti l’elenco delle regole scattate, le richieste bloccate e l’insieme attivo, invece di estrarli con grep. Come appare tutto ciò lo mostra la pagina dimostrativa qui sotto.