Sulla mappa degli attacchi c’è una macchia densa sopra un paese, e viene da sé la regola «vietarlo del tutto». Non darà quasi nulla: cambiare il paese di uscita costa all’attaccante qualche minuto, mentre sotto il divieto finiranno i vostri stessi visitatori e voi stessi in viaggio. Peggio ancora: un crawler visibile che arriva da una sola nuvola, dopo il blocco si trasferirà su indirizzi residenziali e diventerà indistinguibile dalle persone.

Qui sotto ci sono cinque passi che su quello stesso server danno davvero un risultato: tagliare gli scanner, limitare il ritmo, non tagliare ciò che serve e, se proprio serve, bloccare nel modo giusto. Tutto si verifica e si configura in una sola sessione.

Passo 1. Guardare chi crea davvero la macchia

Per prima cosa individuate il registro delle richieste. Su un server con più siti access.log è spesso vuoto, mentre le voci finiscono nel registro del singolo host virtuale:

sudo ls -lS /var/log/apache2/*access*.log /var/log/apache2/*requests*.log 2>/dev/null

In tutti i comandi seguenti si sostituisce il file risultato in cima a quell’elenco. Prima gli indirizzi in testa alla giornata, poi che cosa chiede esattamente quello in cima:

sudo awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -20
sudo grep '^203\.0\.113\.' /var/log/apache2/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20

Il secondo comando risponde alla domanda principale. Se nell’output ci sono decine di pagine diverse del vostro sito, è un raccoglitore di contenuti: è innocuo e basta non contarlo come visitatore. Se invece ci sono /wp-login.php, /.env, /phpmyadmin e altre cose che non avete, è uno scanner, e di lui si occupa il passo 2. Per nginx i registri si trovano in /var/log/nginx/, i campi sono gli stessi.

Passo 2. Una prigione per le richieste a ciò che non avete

Il percorso della richiesta non si può falsificare — è l’intenzione stessa, a differenza dell’indirizzo e dello User-Agent. Il filtro /etc/fail2ban/filter.d/web-probes.local:

[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD) /(wp-login\.php|wp-admin|xmlrpc\.php|\.env|\.git/|phpmyadmin|administrator/)
ignoreregex =

La prigione /etc/fail2ban/jail.d/web-probes.local:

[web-probes]
enabled  = true
port     = http,https
filter   = web-probes
logpath  = /var/log/apache2/*access.log
maxretry = 1
findtime = 1h
bantime  = 1d

Qui maxretry = 1 è giustificato: accessi casuali a un pannello di amministrazione altrui non esistono. Applicare e accertarsi che il filtro intercetti le righe nel vostro registro:

sudo fail2ban-regex /var/log/apache2/access.log /etc/fail2ban/filter.d/web-probes.local
sudo systemctl reload fail2ban && sudo fail2ban-client status web-probes

Passo 3. Limitare il ritmo

Lo scanner e il crawler si tradiscono con la velocità, indipendentemente dal paese da cui arrivano. Trenta pagine al minuto da un solo indirizzo è una soglia che non tocca una persona che sfoglia un catalogo:

[http-flood]
enabled  = true
port     = http,https
filter   = apache-overflows
logpath  = /var/log/apache2/site_access.log
maxretry = 30
findtime = 60
bantime  = 1h

Il dettaglio decisivo è quale registro è indicato in logpath. Se nella maschera finisce il registro del pannello di gestione stesso o dell’area di amministrazione, bloccherete voi stessi: una sola sua pagina trascina una decina di richieste a stili e script, e trenta si accumulano in un paio di clic. Per questo al pannello si dà un file di registro separato, che non corrisponde alla maschera della prigione, e ai file statici una durata di cache lunga, perché il browser non li richieda a ogni passaggio di pagina. Il sintomo, se accade comunque: l’HTML arriva, la grafica no, la pagina resta appesa. Togliere il blocco:

sudo fail2ban-client set http-flood unbanip 203.0.113.10

Passo 4. Proteggere ciò che non si deve tagliare

Prima di attivare i blocchi, annotate le eccezioni — altrimenti la prima cosa a finirci sotto sarà ciò che serve:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24 66.249.64.0/19

Che cosa deve assolutamente stare in questa riga e perché:

  • Il vostro indirizzo o la sottorete dell’ufficio. La causa più frequente della perdita di accesso al server è una regola propria, non un attacco;
  • L’intervallo 66.249.64.0/19. Il robot di ricerca di Google si identifica con lo User-Agent, ma la parte che esegue JavaScript arriva con un normale Chrome/Linux e non si presenta. Qualsiasi regola del tipo «bloccare il traffico dai centri dati» colpisce proprio quella, dopodiché le pagine escono silenziosamente dall’indice. Un indirizzo sospetto si può verificare con una richiesta inversa: dig -x 66.249.66.1 +short deve restituire un nome nella zona googlebot.com;
  • Gli indirizzi delle notifiche di pagamento. I webhook dei sistemi di pagamento arrivano da server AWS e Azure. Se chiudete quelle nuvole per intero, le conferme di pagamento smetteranno di arrivare, e lo scoprirete quando un cliente scriverà che il denaro è stato addebitato ma l’accesso non c’è. Gli intervalli si prendono dalla documentazione del vostro fornitore di pagamenti.

Passo 5. Se il blocco serve comunque, bloccate una sottorete, non un paese

Un addensamento stabile proveniente da una sola rete, che si mantiene per giorni e si accompagna a richieste tutte uguali, è un unico esecutore che procede lungo un elenco. Questo caso si blocca in modo puntuale e a tempo:

sudo ufw deny from 198.51.100.0/22 to any
sudo ufw status numbered

Con il secondo comando annotate il numero della regola: il blocco dovrà essere tolto proprio con quello, sudo ufw delete <numero>. Mettetevi d’accordo con voi stessi su un termine — per esempio un mese — e segnate la rimozione in calendario. Le regole messe «per sempre» dopo un anno si trasformano in un elenco che nessuno osa toccare, perché nessuno ricorda per cosa quegli indirizzi ci siano finiti.

Per scoprire quale sottorete bloccare, partite dall’indirizzo del primo passo: whois 198.51.100.17 | grep -iE 'route|cidr|netname'.

Che cosa cambia dopo questi cinque passi

Gli scanner vengono tagliati per intenzione e non per provenienza, e si prendono alla prima richiesta. I crawler si scontrano con il limite di ritmo. Il robot di ricerca e le notifiche di pagamento continuano a passare. L’unico blocco rimasto ampio è puntuale, con un termine noto e una ragione chiara.

La mappa, intanto, non sparisce, ma il suo ruolo cambia: serve non a chiudere i paesi, ma ad accorgersi dell’addensamento stesso e a confrontarlo con quello di una settimana prima. Per questo serve uno storico e non un’istantanea della giornata in corso. Come appare una mappa così insieme al registro dei blocchi e all’elenco delle porte aperte lo mostrano le pagine dimostrative qui sotto.