2026-08-14 | Pinperepette

XSS2Shell: Da un XSS al Server

Una nuova falla nel core di WordPress permette di passare da un semplice XSS alla esecuzione di codice sul server. Qui ti spiego passo passo come funziona e ti do il codice per provarla in un lab sicuro.

WordPress XSS RCE Lab

// 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.

01
XSS
Il nome utente inserito in wp-login.php viene ristampato senza controlli
02
DOM Clobbering
Il codice malevolo sovrascrive una variabile del browser
03
REST API
Viene creata una Application Password per l'admin
04
Exfil
La password viene rubata dal server dell'attaccante
05
RCE
Con la password si crea un admin, si carica un plugin e si esegue codice

// 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.

$ cd xss2shell-lab $ docker compose up -d [+] Container WordPress su 127.0.0.1:8080 $ source .venv/bin/activate $ python run-poc.py

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.

1const 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:

  1. Crea un nuovo utente admin chiamando /wp/v2/users.
  2. Fa login con questo utente per ottenere i cookie di sessione.
  3. 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.

$ python run-poc.py [*] Login admin su WordPress... [*] Admin loggato. [*] Exploit avviato, attendo il popup... Application Password creata: Xyd8 gD2b 640c t8Cr jMXc L6VO Risposta server: { "status": "pwned", "marker_ok": true } [*] Marker RCE: 200 PWNED at ...

// 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.

$ wp core verify-checksums $ wp plugin verify-checksums --all $ wp user list --role=administrator $ find wp-content -name "*.php" -newermt "14 days ago" $ find wp-content/uploads -name "*.php" $ grep -rEl "eval\(|base64_decode|gzinflate|str_rot13|assert\(|move_uploaded_file" wp-content

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.