Un sito WordPress può sembrare a posto e non esserlo. Home in HTTP 200, titolo giusto, nessuna scritta "Hacked by" in prima pagina. Sotto, un estraneo entra ed esce quando vuole.
Il mese scorso ci è arrivato un sito così. Lo raccontiamo perché il modo in cui l'abbiamo pulito fa capire come funziona la sicurezza di un sito gestito: non un antivirus che gira una volta, ma un canale per intervenire dall'esterno, con backup a ogni passo, senza mai chiedere al cliente di aprire il cofano.
Cosa abbiamo trovato
Il primo indizio era un numero. Otto amministratori. Un sito aziendale normale ne ha uno, forse due. Otto no.
Sette di quegli otto avevano un nome che parla da solo: w2s_ seguito da otto caratteri casuali, con email @wp2shell.local. Account creati nell'arco di due settimane, uno ogni pochi giorni. Non utenti veri: chiavi di riserva che l'attaccante si era lasciato dietro, per rientrare anche se una porta veniva chiusa.
Poi il vettore. Tra i plugin attivi c'era wp2shell — una web shell, un pannello di controllo che dà all'attaccante accesso completo al sito via browser. Accanto, due finti plugin site-tweaks col solito suffisso casuale e nessun numero di versione: la firma del malware, che non si registra su nessun repository ufficiale.
I contenuti erano puliti — nessun codice iniettato nei post, niente redirect nascosti. L'attaccante non voleva defacciare il sito. Voleva restare dentro, invisibile.
Perché non è bastato "controllare i file"
Qui sta il punto che sfugge a molti. Ripulire i file è metà del lavoro.
Il malware WordPress moderno vive nel database. Gli account fantasma stanno in wp_users. Le chiavi di accesso alternative stanno negli application password. Gli hook malevoli si nascondono nel cron. Un pannello che scansiona solo il filesystem e dichiara "sito pulito" ha guardato una stanza sola di una casa con dieci stanze.
Per questo, quando abbiamo finito, l'ultima cosa non è stata un file. È stata una scansione completa del database — utenti, contenuti, opzioni, cron, commenti, trigger MySQL. Ogni superficie dove un intruso può nascondere qualcosa che il tempo non cancella.
Come l'abbiamo pulito senza accessi diretti
Del sito non avevamo cPanel, né FTP, né SSH. Avevamo una cosa sola: il nostro bridge, un piccolo file che il cliente installa e che apre un canale sicuro tra noi e il suo WordPress. Da lì è passato tutto.
L'ordine conta, quando si smonta una casa mentre l'inquilino abusivo è ancora dentro. Prima il backup del database, sempre, prima di toccare qualsiasi cosa. Poi la web shell, spenta rimuovendola dai plugin attivi. Poi i sette account fantasma, cancellati con backup delle righe e i contenuti riassegnati a un utente legittimo. Poi i file del malware, sovrascritti con uno stub inerte, l'originale conservato accanto in caso di ripensamento.
A quel punto l'attaccante era fuori dalla porta ma poteva avere ancora le chiavi in tasca: le password, magari copiate settimane prima. Per questo abbiamo ruotato le chiavi di sicurezza di WordPress — un gesto che espelle tutte le sessioni aperte, la sua compresa — e resettato la password dell'amministratore vero, quello che poteva essere l'account bucato all'origine.
Infine gli aggiornamenti. Undici plugin portati all'ultima versione, uno alla volta, controllando che la home restasse viva dopo ognuno. Il plugin da cui era probabilmente entrato l'attaccante, un file manager con vulnerabilità note, l'abbiamo disattivato e lasciato lì spento: aggiornarlo non serviva, tenerlo attivo era il problema.
Cosa impari da un caso così
Che un sito in HTTP 200 può essere occupato. Che la pulizia vera comincia sul disco e finisce nel database, dove il tempo non cancella niente da solo. Che tra spegnere una backdoor e cambiare una password c'è un ordine, e sbagliarlo lascia buchi.
E che avere un canale per intervenire dall'esterno — con backup a ogni passo e senza chiedere al cliente di mettere le mani nel motore — cambia i tempi di un recupero. Non giorni di attesa per gli accessi. Le ore di un pomeriggio.
Il sito è tornato online lo stesso giorno. L'estraneo è rimasto fuori.