Fail2ban su Ubuntu: installazione e configurazione per SSH e WordPress

Un server esposto a internet viene scoperto in pochi minuti e da quel momento riceve richieste continue: script che provano password su SSH, bot che cercano wp-login.php e xmlrpc.php su ogni sito WordPress. Non si può impedire che bussino, ma si può far sì che chi insiste venga bloccato in automatico. È il lavoro di Fail2ban.

In questa guida lo installiamo su Ubuntu e lo configuriamo con tre jail: una per SSH e due per WordPress (tentativi di login e attacchi a XML-RPC), partendo da una configurazione reale e con i dati che abbiamo visto nei log.


1. Come funziona

Fail2ban legge i file di log, cerca righe che corrispondono a un tentativo fallito (con un’espressione regolare chiamata filtro) e, quando lo stesso indirizzo IP supera una soglia, lo blocca per un certo tempo con il firewall del sistema. Gli ingredienti sono quattro:

  • Jail: l’unità di lavoro, che dice quale log leggere, con quale filtro e con quali soglie.
  • Filtro: l’espressione regolare che riconosce il tentativo fallito.
  • maxretry, findtime, bantime: quanti tentativi, in quanto tempo e per quanto tempo si banna. Con i valori 5, 10m e 1h, chi sbaglia 5 volte in 10 minuti resta fuori un’ora.
  • Azione: cosa si fa al momento del ban. Sul server di questo esempio, un Ubuntu recente, il blocco avviene con nftables.

2. Installazione

sudo apt update
sudo apt install fail2ban
sudo systemctl enable --now fail2ban
sudo fail2ban-client status

L’ultimo comando deve rispondere con l’elenco delle jail attive. Su Ubuntu è già attiva di serie la jail sshd, perché SSH scrive nel journal di sistema e Fail2ban lo legge da lì, senza bisogno di indicare un file di log.

Importante: non modificare mai /etc/fail2ban/jail.conf, perché viene sovrascritto a ogni aggiornamento del pacchetto. Le tue impostazioni vanno in un file separato, /etc/fail2ban/jail.local.


3. I filtri per WordPress

Per SSH il filtro esiste già. Per WordPress dobbiamo crearne due, e conviene farlo prima di scrivere le jail, altrimenti Fail2ban segnala un errore perché non trova il filtro.

sudo tee /etc/fail2ban/filter.d/wordpress-login.conf > /dev/null <<'EOF'
[Definition]
failregex = ^<HOST> .* "POST /wp-login\.php.*" 200
ignoreregex =
EOF

sudo tee /etc/fail2ban/filter.d/wordpress-xmlrpc.conf > /dev/null <<'EOF'
[Definition]
failregex = ^<HOST> .* "(POST|GET) /xmlrpc\.php.*" 403
ignoreregex =
EOF

Cosa riconoscono:

  • Login: un POST a wp-login.php che riceve risposta 200. WordPress risponde 200 quando ripropone il modulo dopo una password sbagliata, mentre un accesso riuscito risponde con un reindirizzamento (302). Quindi il filtro conta solo i tentativi falliti.
  • XML-RPC: una richiesta a xmlrpc.php che riceve 403. Funziona solo se Nginx blocca davvero quel file con deny all, come nell’articolo sull’ottimizzazione di Nginx. Se non c’è il blocco, il server risponde con un altro codice e la jail non scatta mai.

Una cosa da sapere: se hai cambiato l’indirizzo della pagina di login con un plugin, i bot che provano /wp-login.php ricevono un 404 o un reindirizzamento e il filtro del login non li conta. In quel caso servono altri codici nell’espressione.


4. Il file jail.local

Ora la configurazione principale. Crea il file con sudo nano /etc/fail2ban/jail.local e scrivi:

[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 IL_TUO_IP
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 1w

[sshd]
enabled = true
maxretry = 5

[wordpress-login]
enabled  = true
port     = http,https
filter   = wordpress-login
logpath  = /var/log/nginx/access.log
maxretry = 5

[wordpress-xmlrpc]
enabled  = true
port     = http,https
filter   = wordpress-xmlrpc
logpath  = /var/log/nginx/access.log
maxretry = 3
  • ignoreip: gli indirizzi che non verranno mai bannati. Aggiungi il tuo IP di casa, così non ti blocchi da solo se sbagli qualche accesso. Se hai un IP dinamico, lascia solo gli indirizzi locali.
  • bantime.increment: a ogni nuovo ban dello stesso IP il tempo raddoppia (1 ora, 2, 4, 8…), fino a un massimo di una settimana. I bot che tornano ogni giorno restano fuori sempre più a lungo.
  • port = http,https: le porte che il ban chiude per l’IP bloccato. Per SSH non serve, perché la jail sshd usa già quella giusta.
  • maxretry = 3 per XML-RPC: nessun utente legittimo dovrebbe toccare quel file, quindi la soglia può essere più bassa.

Su un server che non ha WordPress (per esempio una VPS con solo bot o servizi), il file si riduce alla sezione [DEFAULT] e alla jail [sshd].


5. Applicare e verificare

sudo fail2ban-client reload
sudo fail2ban-client status

Devi vedere tre jail: sshd, wordpress-login e wordpress-xmlrpc. Attenzione: se aggiungi una jail e non la vedi nell’elenco, probabilmente Fail2ban non è stato ricaricato. Questo è successo anche a noi: la jail XML-RPC era scritta nel file ma risultava assente finché non abbiamo dato reload.

Per vedere i dettagli di una jail e gli IP bannati:

sudo fail2ban-client status sshd
sudo fail2ban-client status wordpress-xmlrpc
sudo tail -30 /var/log/fail2ban.log

Per controllare che un filtro riconosca davvero le righe del tuo log, prima ancora che ci siano ban:

sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-xmlrpc.conf

Il comando stampa quante righe corrispondono al filtro. Se è 0 pur sapendo che ci sono richieste a xmlrpc.php, il filtro o il codice di risposta non corrispondono.

Dopo un reload puoi vedere nel log molti messaggi already banned per lo stesso IP: è normale. Fail2ban rilegge il vecchio log e rielabora richieste già viste, ma il ban è uno solo.


6. Cosa si vede nella pratica

Pochi minuti dopo l’attivazione, la jail di XML-RPC ha già bannato i suoi primi IP: bot che colpivano xmlrpc.php ogni 10 secondi circa, una cadenza regolare che tradisce uno script. Su SSH, in circa 35 minuti abbiamo contato 17 tentativi da 9 indirizzi diversi, con nomi utente come admin, ubuntu, ftpuser e root. Uno script ha provato perfino un utente chiamato %subdomain%: un segnaposto che il programma non aveva sostituito, la prova che dietro non c’è nessuno.

C’è anche un limite da conoscere. Alcuni bot provano un solo accesso ogni 9-10 minuti dallo stesso IP: restano sotto la soglia di 5 tentativi in 10 minuti e non vengono mai bannati. Se vuoi prenderli, allarga la finestra nella sezione [sshd]:

[sshd]
enabled = true
maxretry = 3
findtime = 1h
mode = aggressive

Con findtime = 1h anche i bot lenti raggiungono la soglia, e mode = aggressive fa contare anche le connessioni interrotte prima dell’autenticazione, quelle che nei log finiscono con [preauth]. Attenzione: in questa modalità anche i tuoi tentativi sbagliati contano, quindi tieni ignoreip aggiornato.


7. Gestire i ban

Se ti sei bannato da solo o vuoi sbloccare un indirizzo:

sudo fail2ban-client set sshd unbanip 203.0.113.10
sudo fail2ban-client set wordpress-login unbanip 203.0.113.10

(203.0.113.10 è un indirizzo di esempio: sostituiscilo con quello vero.) Per cambiare il tempo o le soglie basta modificare jail.local e dare di nuovo sudo fail2ban-client reload.


8. Errori comuni

  • La jail non compare nell’elenco: manca il reload, oppure c’è un errore nel file. Guarda sudo tail -30 /var/log/fail2ban.log: di solito indica il filtro mancante o la riga sbagliata.
  • La jail è attiva ma non banna nessuno: controlla con fail2ban-regex che il filtro riconosca le righe del log. Per XML-RPC verifica anche che Nginx risponda davvero 403.
  • Il percorso del log è sbagliato: se Nginx scrive i log di ogni sito in file separati, il logpath deve puntare a quelli giusti.
  • Ti sei bannato da solo: aggiungi il tuo IP a ignoreip e sbloccalo con unbanip. Se hai perso l’accesso a SSH, entra dalla console web del provider.
  • Un firewall esterno: se il provider offre un firewall nel pannello (come quello di Contabo), funziona in parallelo a Fail2ban e non lo sostituisce, perché lavora sui servizi esposti, mentre Fail2ban reagisce ai comportamenti.

Conclusione

Fail2ban non sostituisce le altre difese: chiavi SSH al posto delle password, XML-RPC bloccato su Nginx, plugin aggiornati. Ma è un livello utile che lavora da solo e non pesa sul server. In pochi minuti hai un sistema che impara dai log e chiude la porta a chi insiste, e dopo qualche giorno i numeri nelle jail mostrano quanto rumore c’era in realtà.

[post-views]

Di Corrado