// Il Bug
Il 6 agosto 2026 WordPress rilascia la versione 7.0.3. Tra le correzioni c'è CVE-2026-64638, una falla con punteggio CVSS 8.9. Non è su un plugin o su un tema. È nel cuore di WordPress, nella pagina che usi per fare login.
Il problema è questo: se provi a entrare con un nome utente che contiene codice JavaScript, WordPress lo mostra di nuovo nella pagina di errore senza controllarlo bene. Il browser esegue quel codice. Questo si chiama XSS, cross-site scripting.
Da solo l'XSS non prende il server. Serve anche che un amministratore sia già loggato e interagisca con una pagina preparata dall'attaccante. Quindi non è un attacco zero-click. Ma se questa seconda condizione si verifica, la catena che parte dall'XSS arriva fino a eseguire codice PHP sul server.
Cosa sappiamo. CVE-2026-64638 è reale. La patch 7.0.3 è uscita il 6 agosto 2026 e WordPress l'ha distribuita anche alle versioni precedenti ancora supportate. Non ci sono attacchi confermati su larga scala, ma i dettagli tecnici e il PoC sono pubblici. Il rischio è alto.
Non farlo su siti altrui. Questo articolo serve a capire la falla e a difendersi. Il codice è pensato per un lab locale, non per attaccare qualcuno.
// La Catena
L'attacco non è un colpo solo. È una serie di passaggi. Se ne manca anche uno, tutto si ferma. Questo è il motivo per cui difendersi è possibile: ci sono più punti dove intervenire.
// Il Lab
Per capire la catena ho preparato un lab con Docker. Parte da WordPress 6.7.0, cioè una versione che precede la 7.0.3 con la patch. Dentro ci sono due piccoli plugin di aiuto: uno abilita le Application Password anche senza HTTPS, l'altro espone un nonce REST via JSONP per replicare ciò che un XSS pre-auth reale potrebbe ottenere.
Tutto il codice è nella cartella xss2shell-lab del repository. Ti servono Docker, Python 3.9+ e Playwright.
Se funziona vedi marker_ok: true e status: "pwned".
// Il Primo Passo: XSS
Quando sbagli password, WordPress ti mostra di nuovo il form di login e ti scrive il nome utente che avevi inserito. Questo è comodo, ma pericoloso se il nome utente contiene codice.
Nel PoC il nome utente non è un nome. È una stringa che contiene tag HTML con uno spazio iniziale:
| 1 | < area id=ajaxurl href="http://127.0.0.1:8080/?rest_route=/..."> |
| 2 | < div id=color-picker class=reset-pass-submit> |
| 3 | < button class="wp-generate-pw color-option">X</button> |
Lo spazio prima del nome del tag serve a ingannare i filtri. <area> senza spazio viene riconosciuto e forse bloccato. < area> con lo spazio passa inosservato a certi controlli, ma il browser lo interpreta comunque come tag.
È una di quelle cose che, quando la vedi nel PoC, ti fai venire il dubbio: aspetta un attimo, davvero basta uno spazio?
// Il Secondo Passo: DOM Clobbering
WordPress usa una variabile JavaScript chiamata ajaxurl per mandare richieste al server. Gli script la leggono come una stringa.
Il browser però ha una stranezza: se nella pagina c'è un elemento HTML con id="ajaxurl", quell'elemento diventa accessibile come window.ajaxurl. Se uno script poi usa questa variabile come stringa, il browser prende il valore dell'attributo href dell'elemento.
Il payload crea quindi un elemento <area id=ajaxurl href="URL-attaccante">. Quando uno script interno usa ajaxurl, finisce per chiamare l'URL scelto dall'attaccante.
Cos'è il DOM clobbering. Il browser trasforma automaticamente gli elementi con un id in variabili globali. Non è un bug di WordPress in sé. Diventa pericoloso quando un'applicazione si fida di queste variabili senza controllarle.
// Il Terzo Passo: Application Password
WordPress ha una funzione chiamata Application Passwords. Serve per dare a un'app esterna una password diversa dalla tua, in modo che possa usare le REST API al posto tuo. Se sei admin, quella password ha i privilegi di admin.
Per crearla serve un nonce REST, cioè un codice temporaneo che dimostra che la richiesta arriva da una pagina fidata. Nel lab questo nonce viene esposto via JSONP per replicare come un XSS reale potrebbe leggerlo: da un endpoint pubblico, da uno script già presente nella pagina, o da una scheda admin aperta.
A questo punto il codice malevolo chiede a WordPress di creare una nuova Application Password. Il browser dell'admin, che ha una sessione attiva, completa l'operazione e restituisce la password generata.
| 1 | const appUrl = WP + '/?rest_route=/wp/v2/users/me/application-passwords' + |
| 2 | '&_method=POST&_wpnonce=' + encodeURIComponent(nonce) + |
| 3 | '&name=XSS2Shell&_jsonp=opener.pwn'; |
// Gli Ultimi Passi: dalla Password al Server
La Application Password arriva al server dell'attaccante. Ora l'attaccante può autenticarsi come admin sulle REST API.
Lo script Python fa tre cose:
- Crea un nuovo utente admin chiamando
/wp/v2/users. - Fa login con questo utente per ottenere i cookie di sessione.
- Carica un plugin ZIP malevolo dalla pagina di installazione plugin.
Il plugin contiene un file pwnplugin.php che, appena attivato, scrive un marker nella root del sito e offre un endpoint con shell_exec('whoami'). Nel lab questo dimostra l'esecuzione di codice. Nella realtà il plugin potrebbe nascondere una web shell, modificare file, creare altri account o rubare dati.
// Automazione
La catena usa più finestre del browser: la pagina principale, un popup per il nonce, un altro popup per la password, e infine le richieste REST. Farlo a mano è scomodo.
Per questo ho scritto run-poc.py con Playwright. Avvia il server attaccante, fa login come admin, clicca il pulsante dell'exploit e aspetta il risultato. Alla fine verifica che il marker RCE sia presente.
// Cosa Controllare
Se hai un sito WordPress e vuoi capire se è stato compromesso con una catena di questo tipo, ecco i controlli principali.
Controlla anche le Application Password nella pagina del tuo profilo utente. Se trovi una voce che non hai creato tu, revocala.
| Controllo | Cosa trovi |
|---|---|
wp user list --role=administrator |
Account admin creati dall'attaccante |
find wp-content/uploads -name "*.php" |
File PHP dove non dovrebbero esserci |
find wp-content -name "*.php" -newermt "14 days ago" |
File aggiunti o modificati di recente |
grep -rE "eval\(|base64_decode|gzinflate" wp-content |
Pattern tipici di web shell |
| Verifica Application Passwords | Password API create di nascosto |
Per prevenire: aggiorna WordPress, riduci gli account admin, disabilita la modifica di plugin e temi da wp-admin, monitora i log delle REST API e usa un WAF con regole per XSS.
// Conclusione
Il messaggio di XSS2Shell è che una falla apparentemente piccola, un XSS sulla pagina di login, può diventare il primo passo per prendere il controllo di un server. Non serve un bug enorme. Basta una catena di passaggi ben costruita.
Il lab lo dimostra: dal nome utente reflectato, al DOM clobbering, alla Application Password, fino al plugin malevolo. Ogni passaggio è visibile e modificabile.
Il codice completo è su GitHub, nella cartella xss2shell-lab. Provalo in locale, smontalo, cambia il payload e guarda dove la catena si ferma.