CASO DI STUDIO · E-COMMERCE

Il webhook fantasma: gli ordini di un negozio WooCommerce copiati per mesi verso un server sconosciuto

  • CLIENTENegozio online di articoli sportivi (dati anonimizzati)
  • PIATTAFORMAWooCommerce

Clienti colpiti da phishing con i dati veri dei loro ordini, file puliti: un webhook e una chiave API creati da un admin fantasma. Chiuso entro 6 ore.

Una ricevuta d’ordine con i dati del cliente evidenziati, agganciata da un grande amo bianco da cui parte una linea rossa tratteggiata verso un server scuro sconosciuto con un punto interrogativo rosso

Il contesto

Un negozio online di articoli sportivi su WooCommerce, con qualche migliaio di prodotti e una media di venticinque ordini al giorno, che salgono molto nei periodi di saldi. Il sito gira su un server virtuale gestito da una piccola agenzia, che cura aggiornamenti e backup; i pagamenti passano da un gateway esterno con pagina di pagamento ospitata, quindi i dati delle carte non transitano mai dal sito. Il negozio è collegato al gestionale di magazzino tramite un’integrazione ufficiale, che riceve gli ordini con un webhook di WooCommerce. Per riservatezza, nome del negozio, settore preciso, numeri e ogni dettaglio che permetterebbe di identificarlo sono stati omessi o modificati.

I sintomi

Il primo segnale non è arrivato dal sito, ma dal servizio clienti. A inizio settembre alcuni clienti hanno telefonato per chiedere se fosse normale ricevere un SMS che chiedeva di pagare pochi euro «per sbloccare la spedizione» del loro ordine. Un messaggio del genere, di per sé, non è una novità: le finte notifiche dei corrieri arrivano a tutti. Questa volta però c’era un dettaglio che non tornava. Il messaggio riportava il numero d’ordine corretto, il nome del prodotto acquistato e l’importo esatto. In qualche caso arrivava anche un’email, curata nella grafica, con l’indirizzo di consegna vero.

Nel giro di una settimana le segnalazioni erano diventate una ventina. L’agenzia aveva eseguito due scanner di sicurezza, verificato l’integrità dei file e controllato la pagina di pagamento alla ricerca di uno skimmer, come quelli descritti nell’articolo sugli skimmer JavaScript nel checkout di WooCommerce. Tutto risultava pulito. L’ipotesi più diffusa era una fuga di dati dal corriere. Il titolare non ne era convinto: alcuni degli ordini citati negli SMS non erano ancora stati affidati al corriere. Ci ha contattato scegliendo il servizio con intervento entro 6 ore, perché i dati dei clienti potevano essere ancora in uscita.

Cosa abbiamo trovato a livello applicativo

Le verifiche dell’agenzia erano corrette: core, plugin e tema corrispondevano ai pacchetti originali, nella cartella degli upload non c’erano file PHP, la cartella mu-plugins era vuota e nel database non c’erano opzioni con codice nascosto. Il sito, dal punto di vista dei file, era pulito. Ma un dato che esce da un negozio WooCommerce non ha bisogno di codice malevolo: WooCommerce stesso ha due meccanismi pensati proprio per consegnare ordini a sistemi esterni, i webhook e le chiavi della REST API. Sono configurazione, record nel database, e nessuno scanner di file li guarda.

Abbiamo quindi elencato entrambi, insieme agli account con privilegi elevati:

# Webhook configurati: destinazione, evento, autore e data di creazione
wp db query "SELECT webhook_id, name, status, topic, delivery_url, user_id,
  date_created, failure_count FROM wp_wc_webhooks ORDER BY date_created;"

# Chiavi della REST API: a chi appartengono, permessi e ultimo utilizzo
wp db query "SELECT k.key_id, k.user_id, u.user_login, k.description,
  k.permissions, k.truncated_key, k.last_access
  FROM wp_woocommerce_api_keys k LEFT JOIN wp_users u ON u.ID = k.user_id;"

# Account con ruolo di amministratore o di gestore del negozio
wp user list --role__in=administrator,shop_manager --fields=ID,user_login,user_email,user_registered

I webhook attivi erano due, entrambi sugli eventi di creazione e di aggiornamento degli ordini. Il primo, «Gestionale magazzino», puntava al servizio del gestionale ed era stato creato dall’agenzia due anni prima. Il secondo si chiamava «Sincronizzazione magazzino»: un nome quasi identico, scelto per confondersi con quello vero. Puntava a un dominio registrato pochi giorni prima della creazione del webhook, ospitato su un server virtuale all’estero, ed era stato creato ad aprile, cinque mesi prima, dall’utente con ID 41.

L’utente 41 era un amministratore con un nome che ricordava quello dell’agenzia, qualcosa come «supporto-web», e un indirizzo email su un servizio di posta gratuito. Nessuno lo conosceva. Era stato registrato ad aprile come semplice cliente del negozio, e pochi minuti dopo il suo ruolo era diventato amministratore. Aveva fatto un solo accesso al pannello, durato meno di dieci minuti: il tempo di creare il webhook e una chiave della REST API con permesso di lettura, descritta come «Integrazione spedizioni». La chiave risultava usata l’ultima volta due giorni prima del nostro intervento. È lo schema descritto nell’articolo sugli amministratori fantasma, con una differenza: qui l’account non serviva per tornare sul sito, ma solo per lasciare una configurazione che lavorasse da sola.

Le due configurazioni avevano compiti complementari. Il webhook inviava in tempo reale ogni ordine nuovo o modificato, con tutto ciò che WooCommerce mette nel contenuto della consegna: nome, email, telefono, indirizzi di fatturazione e spedizione, prodotti, importi e stato dell’ordine. La chiave API, invece, era servita a scaricare lo storico: gli ordini degli anni precedenti, pagina dopo pagina. Nessun dato delle carte era coinvolto, perché il sito non li tratta, ma i dati sottratti erano esattamente quelli necessari per un phishing credibile.

Schema del flusso di un ordine. Il cliente completa il checkout e WooCommerce salva l’ordine; la coda delle azioni pianificate consegna l’evento a due webhook con nomi quasi identici. Il primo, Gestionale magazzino, creato dall’agenzia due anni prima, va al gestionale riconosciuto. Il secondo, Sincronizzazione magazzino, creato ad aprile da un amministratore sconosciuto, va a un dominio registrato da pochi giorni su un server all’estero. Sotto, una chiave della REST API in sola lettura, descritta come Integrazione spedizioni, scarica lo storico degli ordini dallo stesso server esterno. Un’annotazione ricorda che nessuno dei tre elementi è un file.
Lo stesso meccanismo, due destinazioni: a distinguere il webhook legittimo da quello malevolo erano solo il dominio, l’autore e la data di creazione.

Cosa abbiamo trovato a livello di sistema

A livello di sistema il server era pulito: nessuna voce estranea nel crontab, nessun processo anomalo, nessuna chiave SSH sconosciuta. Il sistema però conservava le prove di quello che la configurazione aveva fatto, e le abbiamo raccolte da quattro fonti diverse.

La prima era la coda delle azioni pianificate. WooCommerce non consegna i webhook durante il salvataggio dell’ordine, ma li affida alla coda di Action Scheduler, che conserva le azioni completate per circa un mese. Lì c’era, per ogni ordine dell’ultimo mese, una consegna al gestionale e una al server sconosciuto, entrambe completate.

# Consegne dei webhook rimaste nella coda di Action Scheduler
wp db query "SELECT action_id, status, scheduled_date_gmt, args
  FROM wp_actionscheduler_actions
  WHERE hook = 'woocommerce_deliver_webhook_async'
  ORDER BY scheduled_date_gmt DESC LIMIT 20;"

# Registro delle consegne di WooCommerce (Stato, Log, sorgente webhooks-delivery)
ls -l wp-content/uploads/wc-logs/ | grep webhooks-delivery

La seconda erano i registri delle consegne di WooCommerce, che per ogni invio riportano la destinazione e il codice di risposta: il server esterno rispondeva sempre con un 200, segno che riceveva e accettava i dati. La terza erano le connessioni in uscita: abbiamo creato un ordine di prova e osservato, pochi secondi dopo, i processi PHP del sito aprire una connessione verso l’indirizzo del dominio sconosciuto.

# Connessioni in uscita dei processi PHP subito dopo un ordine di prova
ss -tnp state established '( dport = :443 )' | grep php-fpm

La quarta erano i log di accesso del server web, che l’agenzia conservava compressi per un anno. Filtrando le richieste all’interfaccia degli ordini della REST API è emerso un solo indirizzo esterno oltre a quello del gestionale: nelle ore successive alla creazione della chiave aveva scaricato l’intero archivio degli ordini, circa trentamila, cento alla volta, e da allora tornava una volta alla settimana per recuperare gli ordini modificati. Per scorrere mesi di log e isolare queste sequenze ci siamo fatti aiutare da un modello di intelligenza artificiale open-weight eseguito sui nostri server, senza inviare i dati dei clienti a servizi esterni; ogni risultato è stato poi verificato a mano sulle righe originali, come spiegato nell’articolo sugli access log come strumento di indagine.

# Chi ha interrogato l'interfaccia degli ordini, e quante volte
zgrep -h '/wp-json/wc/v3/orders' /var/log/nginx/access.log* \
  | awk '{print $1}' | sort | uniq -c | sort -rn | head

# Richieste dei minuti in cui l'account 41 è passato da cliente ad amministratore
zgrep -h 'admin-ajax.php' /var/log/nginx/access.log-202604*.gz | grep 'POST'

Quest’ultimo filtro ha rivelato anche l’ingresso. Pochi secondi dopo la registrazione dell’account come cliente, dallo stesso indirizzo era partita una richiesta verso una funzione di un plugin per la gestione dei punti fedeltà, installato anni prima e non più aggiornato. La versione presente sul sito aveva una vulnerabilità nota e pubblicata, di tipo escalation dei privilegi: un controllo mancante permetteva a un utente registrato qualunque di modificare il proprio ruolo. La correzione era disponibile da mesi, ma il plugin era stato escluso dagli aggiornamenti automatici dopo che un vecchio aggiornamento aveva rotto una personalizzazione del tema.

Cronologia dell’incidente su una linea del tempo da aprile a settembre. Ad aprile, nell’arco di un’ora: registrazione di un account cliente, escalation del ruolo ad amministratore tramite il plugin dei punti fedeltà non aggiornato, creazione della chiave API e del webhook, download di circa trentamila ordini storici. Da maggio ad agosto: ogni nuovo ordine consegnato in tempo reale al server esterno e un passaggio settimanale della chiave API. A settembre: i clienti ricevono SMS ed email di phishing con i dati veri degli ordini, e il negozio ci contatta. Sotto, le sei ore dell’intervento: raccolta delle prove, disattivazione di webhook e chiave, rimozione dell’account, ricerca dell’ingresso, rotazione delle credenziali, hardening.
Un’ora di lavoro ad aprile, cinque mesi di dati in uscita senza bisogno di tornare sul sito, sei ore per chiudere tutto a settembre.

Come l’abbiamo rimosso

Prima di cambiare qualunque cosa abbiamo salvato le prove: un’esportazione delle tabelle dei webhook, delle chiavi API, degli utenti e della coda delle azioni, i registri delle consegne e le porzioni rilevanti dei log di accesso, con la data e l’ora di ogni estrazione. Poi abbiamo proceduto in quest’ordine:

  1. disattivato il webhook malevolo e revocato la chiave API, per fermare subito l’uscita dei dati, e verificato con un nuovo ordine di prova che non partissero più connessioni verso il server esterno;
  2. tolto il ruolo all’account 41, chiuso tutte le sue sessioni e controllato che non avesse password per le applicazioni; una volta chiusa l’analisi, l’account è stato eliminato;
  3. controllato tutto ciò che un amministratore avrebbe potuto modificare in quei dieci minuti: impostazioni dei gateway di pagamento e dei conti per il bonifico, destinatari delle email di WooCommerce, altri utenti, opzioni e plugin installati, senza trovare altre modifiche;
  4. aggiornato il plugin dei punti fedeltà alla versione corretta, dopo aver verificato con l’agenzia che la personalizzazione del tema non ne dipendesse più;
  5. cambiato le credenziali: password di tutti gli amministratori e dei gestori del negozio, chiavi e salt di WordPress, chiave API e segreto del webhook del gestionale, per sicurezza anche se non risultavano usati da altri.
# Disattiva il webhook sconosciuto, poi lo elimina dopo averne salvato i dati
wp wc webhook update 7 --status=disabled --user=1
wp wc webhook delete 7 --force=true --user=1

# Revoca la chiave API creata dall'account sconosciuto
wp db query "DELETE FROM wp_woocommerce_api_keys WHERE key_id = 3;"

# Chiude le sessioni e rimuove il ruolo prima dell'eliminazione
wp user session destroy 41 --all
wp user set-role 41 subscriber

Sul piano legale, il titolare ha valutato l’incidente con il proprio consulente privacy. Nomi, recapiti, indirizzi e storico degli acquisti di migliaia di clienti erano stati trasmessi a un soggetto sconosciuto per mesi, con conseguenze concrete già visibili: la valutazione ha portato alla notifica al Garante nei termini previsti e a una comunicazione ai clienti, che spiegava cosa era successo e li avvisava di non pagare nessuna richiesta legata agli ordini ricevuta via SMS o email. Il nostro contributo è stato una relazione tecnica con le date, le categorie di dati coinvolte e il numero di ordini esposti, che è ciò che serve a chi deve decidere; il quadro generale è descritto nell’articolo su sito hackerato e GDPR.

L’hardening

L’obiettivo era rendere visibile, la prossima volta, ciò che questa volta era rimasto invisibile per cinque mesi:

  • controllo quotidiano della configurazione: uno script confronta ogni notte webhook, chiavi API, amministratori e gestori del negozio con un elenco di riferimento approvato, e manda un avviso a titolare e agenzia per ogni differenza;
  • connessioni in uscita limitate: il firewall del server consente ai processi PHP di collegarsi solo al gateway di pagamento, al gestionale, al corriere, al servizio email e ai server degli aggiornamenti;
  • registro delle attività degli amministratori, inviato fuori dal server, con avviso immediato alla creazione di un utente con ruolo elevato o al cambio di ruolo di un utente esistente;
  • autenticazione a due fattori obbligatoria per amministratori e gestori del negozio;
  • aggiornamenti senza eccezioni silenziose: un plugin escluso dagli aggiornamenti automatici resta in un elenco con una data di revisione, e i bollettini di sicurezza vengono controllati ogni settimana;
  • revisione dei plugin: tre plugin non più necessari, tra cui un vecchio modulo di importazione, sono stati rimossi.

Il risultato

Webhook e chiave sono stati disattivati entro la prima ora, e l’intero intervento, dall’analisi all’hardening, si è chiuso entro le 6 ore previste dal servizio. Nei giorni successivi il controllo notturno della configurazione non ha segnalato differenze e il firewall non ha registrato tentativi di connessione verso destinazioni non autorizzate. Le segnalazioni di phishing sono proseguite per qualche giorno, con dati sempre precedenti all’intervento, e poi si sono esaurite.

Il titolare ha ricevuto un rapporto tecnico con la ricostruzione dell’attacco, le prove raccolte, l’elenco degli interventi e le nuove procedure, e una relazione separata per il consulente privacy. L’agenzia ha aggiunto il controllo di webhook e chiavi API alla verifica che esegue sugli altri negozi che gestisce.

Cosa insegna questo caso

Quando si pensa alla persistenza di un attacco si pensa a file: una backdoor negli upload, un plugin falso, un cron nascosto. Qui la persistenza era tutta nella configurazione. Un webhook e una chiave API sono funzioni legittime, create dall’interfaccia standard, che sopravvivono alla reinstallazione del core, al ripristino dei file da un backup e agli scanner, perché dal punto di vista del software non c’è nulla di anomalo. Come spiega l’articolo sui limiti degli scanner di sicurezza, uno strumento trova solo ciò che è progettato per cercare.

Il secondo insegnamento riguarda i sintomi. Il sito funzionava perfettamente, e a dare l’allarme sono stati i clienti. Quando un phishing cita dati che solo il negozio possiede, come il numero d’ordine o l’importo esatto, la domanda giusta non è «da quale corriere sono usciti», ma «quali sistemi ricevono i nostri ordini, e chi li ha configurati». Rispondere richiede pochi minuti: basta elencare webhook, chiavi API e amministratori, e chiedersi per ognuno chi l’ha creato e perché.

Rimozionemalware.com è il servizio di bonifica dei siti WordPress e WooCommerce di Managed Server S.r.l., attiva dal 2005 nella gestione di server e hosting. Analizziamo file, database, configurazione e sistema, con l’aiuto di modelli di intelligenza artificiale eseguiti in locale, e garantiamo la pulizia entro 6, 12 o 24 ore per installazione, calcolate dalla ricezione degli accessi. Se i tuoi clienti ricevono messaggi che sanno troppo dei loro ordini, puoi contattarci.

IL PROSSIMO PASSO

Il tuo sito
è infetto?

Ogni ora conta: il malware continua a colpire visitatori e clienti, e Google, l’hosting e le blacklist non aspettano. Scegli il piano, paga online e condividi gli accessi: la bonifica parte subito, a prezzo fisso e con tempi garantiti.

Richiedi la bonifica

Ti porta ai piani: bonifica entro 6, 12 o 24 ore dalla ricezione degli accessi.

MANAGED SERVER S.R.L.info@rimozionemalware.com +39 02 5656 9681

Lunedì – venerdì · 09:30 – 19:30

Preferisci un modulo online?
Contattaci su managedserver.it ↗