Campagna XSS su WordPress installa account amministratore nascosti
Una campagna XSS su WordPress osservata per la prima volta da Patchstack il 4 ottobre 2026 sta utilizzando vulnerabilità di cross-site scripting memorizzato in due plugin non correlati per installare un account amministratore che i proprietari del sito non possono vedere in wp-admin. Lo stesso payload JavaScript, distribuito attraverso entrambe le vulnerabilità, viene caricato da imgcdn1.com ed è stato recuperato e analizzato da Patchstack. Si tratta di un impianto di post-sfruttamento e persistenza progettato per essere eseguito nel browser di un amministratore autenticato, abusare di quella sessione per installare un plugin dannoso, creare un amministratore nascosto, aggiungere un URL di accesso backdoor e riferire i risultati a infrastrutture controllate dagli attaccanti.
La prima vulnerabilità sfruttata è CVE-2026-93836, un XSS memorizzato non autenticato in WPC Product Bundles per WooCommerce fino alla versione 8.6.6. La falla consente a un valore di quantità che inizia con un numero valido di superare la convalida portando anche markup controllato dall'attaccante. Il valore viene memorizzato nei metadati dell'ordine WooCommerce e successivamente renderizzato in modo non sicuro. Patchstack ha visto richieste il 4 ottobre fornire il payload attraverso il parametro woosb_ids[…][qty], con un prefisso numerico seguito da un tag script che carica x.js da imgcdn1.com. Il giorno successivo, lo stesso payload è apparso attraverso CVE-2026-94504 in Ninja Forms fino alla versione 3.15.3. Ninja Forms memorizzava gli invii delle textarea e poteva renderizzarli senza una codifica HTML sicura nell'editor amministrativo legacy delle submission. La versione 3.15.4 ha rafforzato l'escape dell'output. L'attaccante ha inviato il payload attraverso il normale endpoint AJAX di Ninja Forms su /wp-admin/admin-ajax.php con action=nf_ajax_submit, inserendo un tag immagine con un gestore onerror che crea un elemento script e carica lo stesso x.js. I due loader differiscono perché gli attaccanti si adattano a ciascun contesto vulnerabile, ma il secondo stadio centralizzato collega entrambi i tentativi a un'unica campagna.
Il file x.js recuperato è uno script di post-sfruttamento e persistenza WordPress costruito appositamente, leggermente offuscato con tabelle di stringhe codificate e lookup di funzioni indiretti. Il suo flusso è: XSS memorizzato in wp-admin, risoluzione dell'origine, check-in con il server di comando e controllo (C2), installazione del plugin, creazione dell'amministratore, recupero di emer-run.php e report. Il JavaScript non ha bisogno di una seconda vulnerabilità lato server; utilizza la legittima funzionalità amministrativa di WordPress attraverso la sessione autenticata esistente dell'amministratore.
Il malware non ruba mai il cookie WordPress dell'amministratore. Poiché il JavaScript viene eseguito nel contesto di sicurezza del sito compromesso, le richieste same-origin a /wp-admin/ portano automaticamente la sessione dell'utente autenticato. Lo script carica pagine WordPress privilegiate, estrae i nonce CSRF, che sono token di sicurezza monouso richiesti dalle azioni amministrative legittime, e li riutilizza per eseguire quelle azioni da solo. I cookie HttpOnly, una protezione del browser che impedisce al JavaScript di leggere i cookie, non aiutano perché lo script non legge mai il cookie; semplicemente sfrutta la sessione.
Prima di qualsiasi azione di persistenza, il malware contatta https://imgcdn1.com/fz/c.php?xa=check&site=. La risposta contiene campi di stato tra cui plugin, admin, emer e skip, che consentono al C2 di decidere quali fasi sono già riuscite per una vittima. Il malware memorizza anche lo stato di esecuzione locale nel browser sotto una chiave di Local Storage che inizia con __xp_v9_, tracciando le fasi riuscite come plugin, admin ed emer, e alla fine segna un compromesso completato come all. I dati di stato vengono inviati usando fetch(), navigator.sendBeacon() e una richiesta immagine di fallback, così il C2 riceve i risultati anche se la navigazione interrompe l'esecuzione.
Usando la sessione della vittima, lo script carica /wp-admin/plugin-install.php?tab=upload, estrae il nonce di upload, scarica un ZIP da https://imgcdn1.com/fz/c.php?xa=pkg e lo invia tramite l'installer di plugin di WordPress. Patchstack ha recuperato e analizzato il pacchetto. Si installa con lo slug wp-smart-thumbnails e contiene quattro file: wp-smart-thumbnails.php (20.728 byte, dropper impacchettato e file manager), emer-run.php (14.330 byte, installer di persistenza), readme.txt (46 byte, esca) e includes/class-thumb-cache.php (41 byte, esca). Il plugin si spaccia per WP Smart Thumbnails 1.2.4 di "MediaPress Labs", un plugin per la cache delle miniature responsive, ma il file di funzionalità contiene solo un controllo ABSPATH, e readme.txt è di due righe. Ogni file ha timestamp di luglio 2026, mesi prima della campagna, il che corrisponde a una retrodatazione deliberata. Il file principale del plugin si apre con un controllo invertito: se ABSPATH è definito, restituisce, quindi il payload non viene mai eseguito mentre WordPress carica il plugin. Installato e attivo, non fa nulla; esplode solo su una richiesta HTTP diretta, come dice il commento dell'attaccante.
Sotto quel controllo c'è un singolo blob base64. Viene decodificato base64, XORato con una chiave di 26 byte, invertito, passato attraverso ROT13, decodificato base64 altre due volte, ruotato di due byte e decompresso con gzuncompress, poi controllato contro un MD5 hardcoded prima di essere eseguito. Quell'ultimo controllo è il controllo di integrità dell'autore, e lo stadio spacchettato da Patchstack corrisponde. Invece di eval(), il dropper registra un wrapper di stream personalizzato e include il sorgente decodificato tramite un URL dyiv://, eludendo le regole che controllano eval() su input decodificato. Lo stadio spacchettato è di circa 37 KB: un file manager grafico intitolato "Moon, tell me if I could", che offre list, upload, delete, rename, mkdir, read e save tramite un'API JSON. Non ha alcun tipo di autenticazione. Nessuna password, nessun token, nessun controllo di capacità; l'unico test di uguaglianza su una chiave è un gestore JavaScript per il tasto Escape. Non ci sono primitive di esecuzione comandi, ma upload e save possono scrivere byte forniti dall'attaccante in qualsiasi percorso scrivibile, sufficienti per depositare una seconda shell.
emer-run.php è dove viene costruita la persistenza duratura. Via HTTP esegue il bootstrap di WordPress, richiede current_user_can('administrator') e restituisce JSON, motivo per cui x.js lo chiama dall'interno della sessione dell'amministratore. Da riga di comando o WP-CLI salta completamente il controllo di capacità. Il suo commento di intestazione dice "hidden admin + magic login (invoked via XSS admin session). Returns JSON only; drops obfuscated MU-plugins." Fa tre cose. Primo, crea un account amministratore fatto per sembrare di routine: un nome utente estratto casualmente da un dizionario di circa cinquanta parole operative innocue come support, updater, maintenance e backup, con un indirizzo email @wordpress.org e una password casuale di 16 caratteri. Secondo, nasconde quell'account al proprietario del sito. Lo script scrive un plugin must-use in wp-content/mu-plugins/class-wp-query-.php, dove l'esadecimale deriva dal nome utente. Quel file installa tre hook: pre_user_query aggiunge AND user_login != '' alla query SQL utente; users_list_table_query_args aggiunge l'account a login__not_in; views_users decrementa i conteggi dei ruoli mostrati sopra l'elenco. L'account non appare in Utenti → Tutti gli utenti, non appare nel filtro Amministratore e non viene conteggiato nei totali sopra l'elenco. È un amministratore completamente privilegiato che il proprietario del sito non può vedere. I plugin must-use vengono caricati a ogni richiesta, non possono essere disattivati da wp-admin e non sono mai elencati nella schermata Plugin; rimuovere wp-smart-thumbnails non li rimuove. Terzo, crea un URL di accesso magico su /wp-login.php?_wplogin=, supportato da un secondo plugin must-use, class-wp-token-validate.php. Il token vive nell'opzione fz_emer_login_tokens, e il plugin aggancia init a priorità 1 per scambiarlo con un cookie di sessione. Quel token non è legato all'account nascosto; è legato all'amministratore più vecchio esistente sul sito, quindi chiunque possieda l'URL viene silenziosamente autenticato come il proprietario originale, in una sessione indistinguibile da una reale per i log che registrano solo un ID utente. Entrambi i plugin must-use sono scritti offuscati, come blob XOR più gzdeflate con nomi di variabili casuali e una chiave nuova per sito, quindi nessun sito infetto condivide un hash. Ognuno viene poi retrodatato: lo script esamina la root di WordPress, prende il tempo di modifica più vecchio che trova e lo applica al file appena depositato. Gli impianti finiscono per sembrare i file più vecchi del sistema, vanificando la solita ricerca di file modificati di recente.
Una singola esecuzione riuscita dell'XSS memorizzato lascia all'attaccante quattro vie indipendenti per rientrare nel sito, solo una delle quali è visibile dalla dashboard di WordPress: l'amministratore visibile creato tramite user-new.php con credenziali fornite dal C2; l'amministratore nascosto creato da emer-run.php e nascosto da un plugin must-use; l'URL di accesso magico, che autentica il possessore come l'amministratore più vecchio del sito; e il file manager non autenticato nel plugin stesso, raggiungibile tramite richiesta diretta. Rimuovere il plugin vulnerabile, o anche quello dannoso, non chiude nessuna delle ultime tre.
L'XSS memorizzato si adatta a questa campagna perché elimina la parte più difficile dell'attacco: convincere un utente privilegiato a collaborare. Un XSS riflesso richiede che la vittima visiti un URL controllato dall'attaccante, mentre un XSS memorizzato è già dentro l'applicazione, seduto nei dati che gli amministratori aprono come parte del loro lavoro: un ordine WooCommerce nel primo caso, una submission di Ninja Forms nel secondo. Scatta nel momento in cui guardano. Questa distinzione è diventata importante di recente per WordPress Core. A settembre 2026, WordPress 7.1.1 ha corretto un XSS memorizzato non autenticato in wpautop() che permetteva a script controllati dall'attaccante di persistere attraverso il contenuto dei commenti, soggetto all'approvazione dei commenti. Il modello di attacco è: l'attaccante invia dati, WordPress li memorizza, l'attaccante aspetta, l'amministratore li visualizza, il payload viene eseguito in wp-admin, compromissione persistente. Quale plugin fornisce l'XSS iniziale conta poco; una volta che il JavaScript viene eseguito all'interno di un'origine amministrativa autenticata, lo stadio di post-sfruttamento è identico, motivo per cui questi attaccanti collezionano vulnerabilità XSS memorizzate invece di costruire una catena di malware separata per ogni plugin.
Un singolo dominio trasporta ogni fase della campagna. Gli endpoint su imgcdn1.com includono /fz/x.js (impianto di post-sfruttamento basato su browser), /fz/c.php?xa=check (stato della vittima e assegnazione compiti), /fz/c.php?xa=pkg (pacchetto plugin dannoso), /fz/c.php?xa=admin_creds (credenziali amministratore scelte dall'attaccante) e /fz/c.php (report di compromissione). Il dominio è stato registrato il 1° ottobre 2026, e un servizio pubblico di indicizzazione DNS lo elenca tra i domini delegati al nameserver Cloudflare hazel.ns.cloudflare.com il 2 ottobre. Patchstack lo ha visto usato nello sfruttamento due giorni dopo. Entrambe le vulnerabilità erano state divulgate pubblicamente il 22 settembre, circa dieci giorni prima che il dominio apparisse; la tempistica corrisponde a un'infrastruttura creata specificamente per questa campagna.
Al momento della pubblicazione, Patchstack ha confermato questa infrastruttura di distribuzione attraverso almeno due vulnerabilità XSS memorizzate separate, e il volume di sfruttamento rimane limitato nella loro telemetria. Nessuno dei due numeri è un tetto; lo stadio di post-sfruttamento è agnostico rispetto al plugin, quindi qualsiasi XSS memorizzato che mette JavaScript davanti a un amministratore WordPress è un vettore candidato per lo stesso impianto. Il 4 ottobre, CVE-2026-93836 è stato preso di mira dagli IP 204.8.96.109, 147.90.235.22 e 90.184.10.74. Il 5 ottobre, CVE-2026-94504 è stato preso di mira da 94.16.115.121 e 43.250.53.42. La prima richiesta nella telemetria di Patchstack è stata osservata il 4 ottobre alle 10:39 UTC, mirata a un URL di prodotto WooCommerce e fornendo il loader attraverso il parametro woosb_ids in un valore URL-encoded contenente un tag script. Gli IP sorgente differiscono mentre l'infrastruttura di secondo stadio e il malware rimangono identici, coerenti con uno sfruttamento distribuito su diversi host di scansione dietro un unico payload centralizzato e server C2. Quattro dei cinque indirizzi sono nodi di uscita Tor che il firewall di Patchstack ha bloccato in precedenza; il quinto, 43.250.53.42, è segnalato per attività di spam e brute-force e appare in altre campagne recenti. Bloccare questi indirizzi non è una mitigazione duratura da sola. I bacini di destinazione differiscono nettamente: WPC Product Bundles ha più di 30.000 installazioni attive, Ninja Forms più di 500.000.
Indicatori di malware elencati da Patchstack: il campione x.js analizzato è di 19.378 byte; SHA-256 af803bc822cf9596b1f1785e1d59642f91f6fbc21f8e9943feefa19a6df8c2a4; MD5 8a4413c1aa058179ff54b95571bcf2a3. I plugin must-use sono offuscati con una chiave nuova per sito e non corrisponderanno a un hash fisso, quindi sono elencati per percorso: /wp-content/mu-plugins/class-wp-query- e /wp-content/mu-plugins/class-wp-token-validate.php. Il payload è https://imgcdn1.com/fz/x.js, il C2 è https://imgcdn1.com/fz/c.php, e lo slug del plugin dannoso è wp-smart-thumbnails. I proprietari dei siti dovrebbero cercare questi indicatori e anche controllare la presenza di account amministratore nascosti, perché l'elenco utenti visibile potrebbe non mostrare il quadro completo.
Per i proprietari di siti che non possono monitorare ogni avviso sui plugin 24 ore su 24, un host WordPress gestito come AEU Hosting può fornire backup automatici e hardening di sicurezza specifico per WordPress per ridurre la possibilità che un aggiornamento mancato si trasformi in un account amministratore nascosto. Patchstack continua a monitorare l'attività di sfruttamento e l'evoluzione del payload per cambiamenti nell'infrastruttura, nell'offuscamento e nelle tecniche di post-sfruttamento, e aggiornerà il suo report man mano che vengono confermati altri plugin interessati. La campagna è un promemoria che l'applicazione di patch non riguarda solo il plugin vulnerabile, perché il livello di persistenza dell'attaccante sopravvive alla rimozione del plugin.