
Эксплойт Proof-of-concept для CVE-2026-64638: отражённый XSS на странице входа WordPress в связке с DOM clobbering для захвата учётной записи администратора и удалённого выполнения кода.
Программное обеспечение: WordPress Core ≤ 7.0.2 (все версии до 7.0.3)
CVSS: 8.9 (Высокий)
CWE: CWE-79 — Некорректная нейтрализация ввода при генерации веб-страницы
Требуется аутентификация: Не требуется (до аутентификации)
Взаимодействие с пользователем: Активное (администратору нужно кликнуть по 1 ссылке)
Воздействие: XSS → Захват учётной записи → Удалённое выполнение кода
WordPress — самая популярная система управления контентом в мире, на ней работает более 40% всех сайтов в интернете. У каждого сайта на WordPress есть страница входа по адресу /wp-login.php — это публичная конечная точка, доступная любому без аутентификации.
Когда пользователь вводит неверное имя пользователя, WordPress выводит сообщение об ошибке, содержащее точное имя пользователя, которое только что ввёл пользователь: «Имя пользователя X не зарегистрировано на этом сайте». Проблема в том, что значение имени пользователя помещается непосредственно в HTML-ответ без какой-либо экранирующей функции — атакующему достаточно ввести HTML/JavaScript вместо настоящего имени пользователя, и код будет выполнен в браузере.
Это отражённая XSS-уязвимость — полезная нагрузка содержится в запросе и отражается сервером в HTML в неизменном виде. Опасность усиливается тем, что уязвимость находится на странице входа — месте, которое часто посещают администраторы и где можно похитить сессионные cookie администратора.
Исследовательская группа также обнаружила, что эту XSS можно объединить в цепочку с уязвимостью DOM clobbering в emoji-loader WordPress, что позволяет загружать JavaScript с внешнего сервера. Далее атакующий может создать новую учётную запись администратора → установить плагин с веб-шеллом → выполнить PHP-код на сервере. Эта цепочка эксплуатации получила название XSS2Shell.
| Атрибут | Значение |
|---|---|
| CVE ID | CVE-2026-64638 |
| CVSS Score | 8.9 (Высокий) |
| Программное обеспечение | WordPress Core ≤ 7.0.2 |
| Аутентификация | Не требуется (до аутентификации) |
| Взаимодействие с пользователем | Требуется 1 клик (администратор кликает по ссылке) |
| Сложность атаки | Высокая |
| Исправлено в | WordPress 7.0.3 (08/06/2026) |
| Сообщивший | команда pwn.ai через HackerOne |
| Отчёт HackerOne | #3877102 |
WordPress загружает поддержку эмодзи на каждой странице (включая страницу входа) через файл emoji-loader.js. Этот скрипт читает конфигурацию из элемента с id="wp-emoji-settings":
// Before patch (vulnerable)
const settings = JSON.parse(
document.getElementById('wp-emoji-settings').textContent
);
document.getElementById() возвращает первый элемент в DOM с подходящим id. Если атакующий внедрит <div id="wp-emoji-settings"> перед исходным тегом скрипта, getElementById прочитает содержимое атакующего вместо реальной конфигурации. Этот приём называется DOM clobbering — переопределение поведения JavaScript путём внедрения HTML-элементов.
Конфигурация эмодзи содержит URL для загрузки JavaScript-файла (concatemoji). Атакующий контролирует этот URL → загружает JS-файл с внешнего сервера → выполняет произвольный код в контексте браузера.
После достижения выполнения JavaScript в контексте администратора атакующий получает все привилегии администратора WordPress:
/wp-admin/user-new.php с сессией администратора/wp-admin/plugin-install.phpЛюбой из 3 указанных методов позволяет выполнять PHP-код на сервере — то есть RCE.
Из фиксирующего коммита 0d6d42e в wordpress-develop я определил 3 места в файле wp-includes/user.php, где имя пользователя/email помещается непосредственно в сообщение об ошибке:
# View diff between vulnerable and patched versions
git diff 7.0.2..7.0.3 -- src/wp-includes/user.php
Место 1 — строка 189 (имя пользователя не существует):
До:
// BEFORE (vulnerable):
sprintf(
__( 'The username <strong>%s</strong> is not registered...' ),
$username // ← no escaping
)

После:
// fixed:
sprintf(
__( 'The username <strong>%s</strong> is not registered...' ),
esc_html( $username ) // ← escaped
)
Место 2 — строка 216 (неверный пароль):
До:

// BEFORE:
'<strong>' . $username . '</strong>' // ← no escaping
После:
// AFTER:
'<strong>' . esc_html( $username ) . '</strong>'
Место 3 — строка 299 (неверный пароль для email):
До:

// BEFORE:
'<strong>' . $email . '</strong>' // ← no escaping
После:
// AFTER:
'<strong>' . esc_html( $email ) . '</strong>'
Поток данных от POST-запроса до сообщения об ошибке:
$_POST['log'] ← user input from login form
↓
wp_signon() [user.php:51]
$credentials['user_login'] = wp_unslash($_POST['log']) ← only removes backslashes
↓
wp_authenticate($username, $password) [pluggable.php:689]
$username = sanitize_user($username) ← strips HTML tags, but has a bypass
↓
wp_authenticate_username_password() [user.php:153]
get_user_by('login', $username) ← user not found
↓
sprintf('The username <strong>%s</strong>...', $username) ← XSS!
↓
WP_Error → login_header() → wp_admin_notice()
wp_kses_post(...) ← filters HTML but allows <div>, <a>, through
↓
HTML response → browser render → JavaScript execute
Перед попаданием имени пользователя в HTML в WordPress действуют 2 уровня фильтрации:
Уровень 1: sanitize_user() — вызывает strip_tags() для удаления HTML-тегов. Однако у PHP-функции strip_tags() есть известные ограничения: нестандартное форматирование тегов может обойти фильтр.
Уровень 2: wp_kses_post() — пропускает безопасное подмножество HTML, включая <div>, <a>, `` с определёнными атрибутами (но удаляет обработчики событий, такие как onerror, onload). Ключевой момент: wp_kses_post разрешает <div id="wp-emoji-settings"> — именно тот элемент, который нужен для DOM clobbering.
Команда pwn.ai нашла способ обойти оба уровня и внедрить рабочую полезную нагрузку. Конкретные технические детали не были публично раскрыты.
Чтобы наглядно подтвердить, что имя пользователя попадает в HTML без экранирования, я использовал Xdebug + VS Code и установил точки останова в ключевых точках цепочки выполнения.
Шаг 1 — Ввод XSS-полезной нагрузки в форму входа:
Откройте http://localhost:8282/wp-login.php, введите имя пользователя `` и нажмите Log In. Появляется всплывающее окно alert — XSS работает.


Шаг 2 — Точка останова на user.php:184 — где происходит XSS: