Case study: bonifica malware, spam SEO e hardening di un e-commerce WordPress
Un sito WordPress compromesso non è necessariamente offline, visibilmente alterato o pieno di errori. Può continuare a vendere, mostrare la homepage corretta alla maggior parte degli utenti e conservare, nello stesso tempo, backdoor attive, processi persistenti e contenuti differenti per i crawler.
È ciò che abbiamo riscontrato durante un intervento su un ecommerce italiano. Il primo segnale era apparentemente SEO: nei risultati di Google compariva un titolo estraneo, collegato a contenuti di gioco d’azzardo. L’indagine ha però ricostruito una compromissione molto più profonda, con esecuzione di codice, amministratori abusivi, web shell, movimento laterale tra installazioni WordPress, persistenza a livello di sistema e acquisizione fraudolenta di Google Search Console.
In questo case study descriviamo il metodo seguito dalla nostra web agency: diagnosi, acquisizione forense, contenimento, bonifica, rotazione delle credenziali, isolamento degli ambienti, hardening e monitoraggio. Il caso è completamente anonimizzato: non riportiamo nomi, domini, date precise, indirizzi IP, percorsi reali o altri indicatori che possano rendere riconoscibile il cliente.
Il segnale iniziale: uno spam SEO che nascondeva una compromissione completa
La homepage, aperta con un browser normale, appariva corretta. Non mostrava il contenuto spam e non reindirizzava sistematicamente l’utente. Questo è un elemento importante: l’assenza di anomalie visibili sul frontend non dimostra l’integrità del sito.
La campagna malevola era costruita su più livelli:
- una landing HTML di oltre un megabyte inserita nella document root, con migliaia di keyword estranee e migliaia di link verso la homepage legittima;
- un canonical che attribuiva la landing al dominio del cliente;
- relazioni
amphtml,alternateehreflangverso una pagina esterna controllata dagli attaccanti; - un redirect condizionale attivato da parole presenti nello user-agent, da strumenti di verifica o da uno specifico cookie;
- pulsanti diretti a uno short-link, probabilmente usato per tracciamento, geolocalizzazione e rotazione della destinazione finale;
- un file HTML di Google Site Verification caricato senza autorizzazione.
Il risultato era una forma di parasite SEO con cloaking: il dominio legittimo forniva autorevolezza e segnali di indicizzazione, mentre crawler, tool o utenti instradati attraverso il percorso malevolo potevano ricevere contenuti differenti.
Per questo non abbiamo trattato l’evento come un semplice errore di indicizzazione. Correggere title, sitemap o cache avrebbe rimosso il sintomo più evidente, lasciando intatti l’accesso e la persistenza dell’attaccante.
La metodologia di analisi: partire in sola lettura
Prima di modificare il server abbiamo ricostruito lo stato dell’incidente. In una bonifica, cancellare subito il primo file sospetto è spesso controproducente: si rischia di distruggere la timeline, perdere indicatori utili e lasciare attive copie meno evidenti della stessa backdoor.
Abbiamo quindi correlato:
- log Apache, Nginx, proxy, PHP e WordPress disponibili;
- filesystem delle diverse installazioni;
- crontab del pannello hosting e processi di sistema;
- connessioni di rete attive;
- inventario degli utenti e dei privilegi WordPress;
- metadati, dimensioni, hash e contenuto dei file sospetti;
- dump dei database e ricerche mirate in opzioni, post e metadati;
- confronti con pacchetti ufficiali e copie note come pulite;
- risposte HTTP ottenute come browser normale e come Googlebot;
- proprietari, metodi di verifica e anomalie presenti in Search Console.
Questa correlazione ci ha permesso di separare fatti dimostrati, inferenze ad alta probabilità ed elementi non dimostrabili. È una distinzione fondamentale in una relazione tecnica: sapere che un attaccante poteva leggere il database non equivale a poter provare che ne abbia esportato una copia.
La catena d’attacco ricostruita dai log
I log mostravano richieste POST ripetute verso due forme dello stesso endpoint REST batch:
/?rest_route=/batch/v1
/wp-json/batch/v1
Le richieste ricevevano HTTP 207. Subito dopo, nella medesima finestra temporale, comparivano:
- nuovi utenti amministratori;
- accessi riusciti a
wp-admin; - upload di archivi ZIP tramite le azioni amministrative di installazione di plugin e temi;
- richieste dirette alle web shell appena caricate.
La sequenza prova l’abuso dell’endpoint come parte del vettore di ingresso. Non abbiamo però attribuito con certezza l’intero incidente a uno specifico CVE: per farlo sarebbero serviti dati completi sulle versioni e sul codice effettivamente in uso al momento del primo accesso.
Questa prudenza evita una scorciatoia frequente nelle analisi post-incidente: scegliere una vulnerabilità plausibile e trasformarla, senza prove sufficienti, nella causa dimostrata.
Il movimento laterale favorito dall’utente Unix condiviso
L’infrastruttura ospitava produzione, staging e un’applicazione WordPress secondaria nella stessa subscription. Le installazioni utilizzavano lo stesso proprietario Unix. Una web shell eseguita sull’applicazione meno esposta poteva quindi leggere e modificare anche le directory della produzione.
Il primo ingresso chiaramente documentato riguardava l’applicazione secondaria. Gli orari di creazione degli amministratori abusivi coincidevano con l’upload di plugin e temi contraffatti e con l’apertura delle relative shell. Da lì, i permessi condivisi spiegavano il passaggio alla produzione senza richiedere un secondo accesso SSH o FTP.
Abbiamo classificato questo passaggio come inferenza ad alta confidenza: i permessi rendevano il movimento possibile e la sequenza degli eventi era coerente, ma non esisteva un log applicativo capace di descrivere ogni singolo comando eseguito dall’attaccante.
La lezione architetturale è netta: staging, generatori, sottositi e strumenti interni devono essere isolati dalla produzione. Il loro traffico ridotto non li rende meno rischiosi se possiedono accesso in scrittura alle stesse directory.
Backdoor distribuite tra core, temi, plugin e document root
La compromissione non dipendeva da un solo file. Abbiamo trovato web shell autonome nella root e nell’applicazione secondaria, codice iniettato nel core, un tema alterato, un plugin commerciale modificato e un payload PHP nascosto dietro un’estensione grafica.
Una delle backdoor verificava la presenza di uno specifico cookie e, quando la condizione era soddisfatta, includeva il payload nascosto. La stessa logica era duplicata in più componenti. Era quindi possibile rimuovere una copia e lasciare funzionante l’altra.
Abbiamo inoltre trovato un file .htaccess collocato dentro la directory di un plugin per riabilitare l’esecuzione PHP in un punto nel quale il comportamento normale avrebbe dovuto essere più restrittivo.
I timestamp non sono stati usati come prova assoluta della data iniziale dell’incidente. Un attaccante può preservare o manipolare le date dei file; per questo abbiamo dato maggiore peso alla correlazione tra log, creazione degli utenti, upload e prime richieste alle shell.
La persistenza non era limitata a WordPress
Nel pannello del server era presente un’attività pianificata con un nome apparentemente legittimo. Il task avviava ogni giorno un binario collocato nella home dell’utente della subscription.
Durante l’analisi abbiamo osservato:
- un processo eseguito direttamente dalla home dell’utente web;
- un secondo processo caricato in memoria con un nome progettato per ricordare un servizio di sistema;
- una connessione TLS in uscita verso un’infrastruttura remota;
- eseguibili cancellati dal filesystem ma ancora aperti dai processi.
Prima di terminare i processi abbiamo recuperato i binari ancora accessibili tramite /proc. Un file cancellato può continuare a essere eseguito finché il processo mantiene aperto il relativo inode: limitarsi a una ricerca nel filesystem avrebbe perso proprio una parte delle evidenze più importanti.
Questo passaggio spiega perché una bonifica seria non può fermarsi allo scanner WordPress. Bisogna esaminare almeno cron, processi, porte, connessioni e persistenze dell’utente di sistema.
Acquisizione forense e contenimento
Confermata la compromissione, abbiamo sospeso temporaneamente la subscription. Durante il contenimento abbiamo verificato una risposta HTTP 503 sia per il browser normale sia per Googlebot, evitando che il server continuasse a distribuire contenuti o a eseguire richieste applicative mentre intervenivamo.
Prima della pulizia abbiamo acquisito:
- snapshot di processi, connessioni, porte e file aperti;
- configurazioni web, PHP, WordPress e pannello hosting;
- crontab e attività pianificate;
- tre dump compressi dei database;
- web shell, componenti alterati, landing SEO e file di verifica;
- binari recuperati dai processi, compresi quelli non più presenti nella directory originale;
- estratti dei log relativi al vettore di ingresso;
- hash SHA-256 dei reperti principali.
Abbiamo conservato tutto in un archivio root-only, non servito dal web. I reperti non sono backup da ripristinare: devono essere analizzati in un ambiente isolato e non devono tornare nelle directory pubbliche.
Solo dopo l’acquisizione abbiamo sospeso i cron legittimi, terminato i processi malevoli, verificato la chiusura della connessione remota e messo in quarantena il meccanismo di persistenza.
La bonifica: sostituzione verificata, non editing alla cieca
Non abbiamo tentato di “ripulire” a mano i file core e i componenti ufficiali. Abbiamo scaricato via HTTPS pacchetti puliti, verificato le versioni e sostituito integralmente le directory compromesse.
In particolare abbiamo:
- sostituito le directory del core e verificato i checksum;
- ripristinato temi e plugin ufficiali da pacchetti puliti;
- messo integralmente in quarantena i componenti che contenevano codice iniettato;
- revisionato il plugin personalizzato dell’applicazione secondaria, controllando nonce, capability check e sanitizzazione;
- rimosso web shell, landing SEO, file di verifica e amministratori abusivi;
- cercato gli indicatori in post, meta, transient e opzioni;
- verificato l’integrità delle tabelle dei database;
- eseguito un confronto ricorsivo tra componenti ripristinati e fonti pulite.
Il confronto ricorsivo finale non ha mostrato differenze residue nei componenti ufficiali sostituiti. Per i plugin commerciali o personalizzati, privi di checksum pubblici affidabili, abbiamo usato copie note, revisione del codice e confronto con repository e ambienti di riferimento.
Questo approccio è più solido della correzione puntuale: se non conosciamo tutte le modifiche effettuate dall’attaccante, la sostituzione verificata riduce il rischio di lasciare frammenti della backdoor. Vale anche quando dobbiamo aggiornare WordPress in modo sicuro dopo un incidente.
Rotazione dei segreti e valutazione del possibile accesso ai dati
Il processo PHP compromesso poteva leggere il file di configurazione e ottenere le credenziali del database. Poteva quindi interrogare le tabelle dell’ecommerce e accedere a dati anagrafici, indirizzi, ordini, hash delle password e riferimenti transazionali.
Non abbiamo trovato la prova di un’esportazione del database. Tuttavia, l’assenza di un file SQL non esclude l’esfiltrazione: i dati possono essere letti a blocchi o inviati all’esterno senza creare un dump locale. Nella relazione abbiamo quindi distinto con precisione la capacità tecnica dalla prova dell’evento.
Le verifiche non hanno rilevato numeri completi di carta o codici di sicurezza conservati nel database. I campi sensibili erano gestiti dai gateway, ma restavano presenti riferimenti alle transazioni e segreti applicativi potenzialmente leggibili.
Abbiamo quindi:
- ruotato le credenziali dei database e aggiornato i file di configurazione;
- rigenerato le salt WordPress e invalidato le sessioni esistenti;
- reimpostato le password degli amministratori legittimi;
- randomizzato le credenziali FTP e revisionato le chiavi SSH;
- ruotato token, password SMTP, API e segreti dei servizi esterni;
- verificato operazioni, ordini e rimborsi sui gateway;
- separato la valutazione tecnica dalla valutazione privacy e dal rischio per gli interessati.
Rimuovere la web shell non rende nuovamente segrete le credenziali che essa poteva leggere. La rotazione non è un’attività accessoria: è parte integrante della bonifica.
Hardening WordPress e filesystem
Dopo la pulizia abbiamo applicato misure mirate alle modalità operative emerse durante l’incidente:
wp-config.phpcon permessi600;WP_DEBUGeWP_DEBUG_LOGdisabilitati in produzione;DISALLOW_FILE_EDITattivo per impedire l’editing dal backend;DISALLOW_FILE_MODSsull’applicazione che non doveva installare o aggiornare componenti dawp-admin;- esecuzione PHP bloccata nelle directory di upload;
- XML-RPC disabilitato dove non necessario;
- plugin, temi ed estensioni inutilizzati rimossi dopo backup e verifica;
- account privilegiati ridotti a quelli realmente necessari e protetti con 2FA.
Abbiamo anche rimosso e compresso oltre 14 GB di vecchi debug log, conservandoli nel fascicolo forense. Oltre a recuperare spazio, questo ha eliminato file operativi enormi dalle directory di lavoro senza perdere le evidenze.
Isolamento reale di produzione, staging e applicazioni secondarie
La mitigazione temporanea tramite open_basedir non era sufficiente come soluzione definitiva. Abbiamo separato gli ambienti in subscription e utenti Unix distinti, con credenziali database differenti e shell disabilitate per gli utenti web che non ne avevano bisogno.
Abbiamo verificato con test di lettura e scrittura incrociata che PHP non potesse accedere alle altre document root. Il nuovo staging è stato:
- protetto con autenticazione HTTP prima di WordPress;
- impostato con
noindex,nofollowenoarchive; - configurato come ambiente di staging;
- bloccato a livello applicativo nell’invio di email reali;
- separato dalla produzione anche nel processo di rilascio.
Per tema e plugin personalizzati abbiamo formalizzato repository distinti, branch separati per produzione e staging, pacchetti ZIP puliti, manifest per file e hash SHA-256 dell’archivio. Lo staging non è più la sorgente informale da cui copiare file verso il sito pubblico.
Hardening del server e protezione dell’origine
La produzione è stata instradata attraverso un WAF esterno con collegamento HTTPS completo fino all’origine. Abbiamo poi negato l’accesso diretto al virtual host sull’IP della VPS, autorizzando soltanto le reti ufficiali del proxy e il traffico locale.
Questa configurazione evita un errore comune: installare un WAF ma lasciare l’origine pubblicamente raggiungibile, consentendo a chi conosce l’IP di aggirare completamente il filtro.
Abbiamo inoltre:
- mantenuto il firewall con policy predefinita di blocco in ingresso e sole porte necessarie;
- revisionato tutte le jail del sistema antintrusione;
- configurato le reti del WAF come proxy fidati, evitando ban impropri dei nodi legittimi;
- disabilitato globalmente l’accesso SSH tramite password e autenticazione interattiva;
- mantenuto l’accesso amministrativo esclusivamente tramite chiavi autorizzate;
- limitato MariaDB alle connessioni locali;
- revisionato utenti con UID 0, sudoers, servizi, porte ed estensioni del pannello;
- mantenuto ModSecurity e lo scanner malware server-side come livelli ulteriori di controllo.
Una scansione completa su tutte le subscription ha controllato oltre 370.000 risorse senza rilevare malware. È un riscontro importante, ma non lo abbiamo trasformato in una garanzia assoluta: nessuno scanner, isolatamente, può certificare che un sistema già compromesso sia “definitivamente sicuro”.
Backup e log: distinguere copie pulite e copie compromesse
Abbiamo conservato dump e backup pre-bonifica come materiale potenzialmente contaminato, separandoli dalle copie create dopo il ripristino. Ripristinare automaticamente il backup più recente avrebbe potuto reintrodurre web shell o persistenze già presenti al momento della copia.
La nuova strategia comprende copie locali e offsite cifrate, almeno una copia non modificabile dal server di produzione, test documentati di ripristino e una retention dei log tecnici di almeno 90 giorni.
Un backup WordPress non è affidabile soltanto perché esiste: dobbiamo conoscere origine, data, stato di sicurezza, integrità e reale ripristinabilità.
Come abbiamo verificato la bonifica
Non abbiamo riaperto il sito dopo la sola rimozione dei file. Il collaudo ha incluso:
- homepage richiesta con browser normale e Googlebot;
- confronto di titolo, canonical, meta description, dimensione e contenuto delle risposte;
- analisi delle differenze di hash, ricondotte a elementi dinamici legittimi e non a cloaking;
- test dei vecchi percorsi malevoli, tutti in risposta
403o404; - verifica dell’assenza di processi, porte e connessioni legati agli indicatori conosciuti;
- ricerca di PHP world-writable e file eseguibili negli upload;
- checksum del core e confronto dei componenti ripristinati;
- integrità delle tabelle e inventario finale degli amministratori;
- controllo di proprietari e metodi di verifica Search Console;
- test di frontend, login, carrello, checkout, API e webhook.
Le risposte del browser e di Googlebot avevano gli stessi metadati e la stessa struttura. Le piccole differenze di hash dipendevano dall’ordine casuale di alcuni prodotti, non da una condizione sullo user-agent. È così che abbiamo escluso il cloaking residuo, non limitandoci a una singola apertura manuale della homepage.
Monitoraggio post-incidente basato su baseline
La bonifica non si è conclusa con la riapertura. Abbiamo creato una baseline post-incidente per codice WordPress, upload eseguibili, configurazione SSH, cron, regole del WAF, sistema antintrusione e componenti del monitor.
Il server genera rapporti diagnostici giornalieri, settimanali e mensili protetti da checksum. Una chiave SSH dedicata può eseguire esclusivamente il lettore dei report tramite comando forzato: non apre una shell, non inoltra porte e non può eseguire comandi generici.
I controlli coprono disponibilità, TLS, WAF, firewall, spazio, inode, backup, amministratori, nuovi PHP, upload eseguibili, checksum, database, servizi e variazioni dei componenti personalizzati.
Il sistema non applica correzioni automatiche. Aggiornamenti, quarantene, modifiche di configurazione e aggiornamenti della baseline richiedono un’approvazione separata. Una baseline viene aggiornata solo dopo avere dimostrato che la variazione è legittima, collaudata e autorizzata.
Cosa possiamo affermare e cosa no
Una relazione tecnica credibile non deve trasformare le ipotesi in certezze. Nel nostro caso abbiamo potuto dimostrare:
- l’uso dell’endpoint batch prima della creazione degli amministratori e degli upload;
- la presenza e l’esecuzione delle web shell;
- la creazione e la verifica riuscita del file Search Console;
- la landing spam, il canonical fraudolento e il cloaking;
- la persistenza pianificata, i processi malevoli e la connessione in uscita;
- le alterazioni di core, temi e plugin;
- la rimozione degli indicatori conosciuti e l’esito dei collaudi finali.
Abbiamo invece classificato come inferenze ad alta probabilità il primo punto d’ingresso, il movimento laterale favorito dall’utente Unix condiviso e il ruolo della connessione remota come canale di comando e controllo.
Non era possibile dimostrare l’identità dell’attaccante, il numero di utenti esposti alla landing, la data assoluta della prima compromissione, l’eventuale esfiltrazione di dati o lo specifico CVE responsabile.
Questa distinzione non indebolisce la relazione: la rende tecnicamente più solida.
Le lezioni operative del caso
- Un frontend normale non equivale a un sistema integro. Dobbiamo verificare anche crawler, log, processi, rete, cron e Search Console.
- La bonifica non è la cancellazione di un file. Richiede conservazione delle prove, contenimento, ripristino da fonti pulite e collaudi.
- Gli ambienti devono avere confini Unix reali. Un sottosito vulnerabile non deve poter scrivere nella produzione.
- I segreti leggibili vanno ruotati. Eliminare la backdoor non revoca automaticamente password, token e chiavi.
- WAF e scanner sono livelli, non soluzioni uniche. Devono lavorare insieme a patch, permessi, backup, logging e monitoraggio.
- I backup possono contenere la compromissione. Le copie pre e post-bonifica devono essere classificate e testate.
- SEO e sicurezza sono collegate. Canonical, AMP, cloaking e proprietà Search Console possono trasformare un accesso tecnico in un danno reputazionale.
- Il monitoraggio deve confrontare lo stato con una baseline approvata. Una scansione occasionale non sostituisce la sorveglianza continuativa.
Conclusione
La parte più visibile dell’incidente era un titolo spam nei risultati di ricerca. La parte più pericolosa era l’architettura che lo rendeva possibile: accesso al codice, persistenza, privilegi troppo estesi e capacità di muoversi tra installazioni differenti.
Noi abbiamo affrontato il caso seguendo una sequenza verificabile: analisi, acquisizione forense, contenimento, bonifica, rotazione dei segreti, isolamento, hardening, collaudo e monitoraggio. È questa disciplina — più della scelta di un singolo plugin — a trasformare una pulizia urgente in un intervento di sicurezza serio e documentabile.
Se sospetti che un sito WordPress sia stato compromesso, o vuoi verificare l’architettura prima che si verifichi un incidente, contattaci. Possiamo analizzare codice, configurazione, log e infrastruttura, definendo un intervento circoscritto, reversibile e documentato.