
Неаутентифицированный отражённый XSS в WPForms <= 2.0.2.1 — обход экранирования Smart Tag из CVE-2026-88996 в позиции имени атрибута
Затронуто: WPForms – AI Form Builder for WordPress (бесплатная/lite), версия 2.0.2.1 — текущая последняя на момент раскрытия (а также версия, которая содержит исправление для CVE-2026-88996). Редакция Pro использует тот же рендерер Smart Tag.
Класс: отражённый межсайтовый скриптинг без аутентификации (CWE-79) с подтверждённым выполнением JavaScript, через неполное исправление CVE-2026-88996 / GHSA-8j5g-9vmv-9mmw.
Обнаружено: 2026-09-27 ·
Сообщено в Patchstack: 2026-09-27 ·
Отклонено: 2026-09-28 («проблема возникает только когда администратор сайта написал голый Smart Tag там, где должно быть имя HTML-атрибута... это не конфигурация по умолчанию и не шаблон, который кто-либо реалистично создал бы» — техническая обоснованность не оспаривалась) ·
Публично: 2026-09-28.
Исправление 2.0.2.1 для отражённого XSS семейства CVE-2026-93485 экранирует значения Smart Tag, которые подставляются в разметку (SmartTags::escape_for_attribute() кодирует кавычки и пробельные символы). Это эффективно всякий раз, когда подставляемое значение попадает внутрь значения атрибута в кавычках. Когда автор формы размещает голый Smart Tag в позиции имени атрибута в сообщении Confirmation — например:
<input {field_id="3"}>
значение отправленного поля, не содержащее кавычек и пробелов, например
onmouseover=alert(1), вставляется дословно и становится новым действующим атрибутом
обработчика событий. Экранирование никогда не кодирует =, а имена атрибутов не требуют ни кавычек, ни
пробелов. Подтверждение, возвращаемое анонимному отправителю, затем содержит:
<input onmouseover=alert(1)>
Анонимные отправки в admin-ajax.php (action=wpforms_submit,
wp_ajax_nopriv_*) по своей конструкции не требуют nonce для неавторизованных пользователей, поэтому это полностью
неаутентифицированный отражённый XSS. Произвольный JavaScript выполняется в рамках ограничения одного токена
через onmouseover=eval(String.fromCharCode(...)).
В той же форме структура полезной нагрузки родительского CVE — разрыв кавычек изнутри заключённого в кавычки
значения, например x" onfocus=alert(1) autofocus y=" — корректно экранируется в каждой
позиции (наблюдается кодирование " /  ). Экранирование в 2.0.2.1 работает везде,
кроме размещения в имени атрибута. Это неполное исправление того же самого стока подстановки,
а не отдельный пробел в функциональности.
# 1. WordPress + WPForms lite 2.0.2.1, create a form with a single-line text field (id 3)
# 2. Set the form's Confirmation message to:
# A: <input value="{field_id="3"}"> B: <input {field_id="3"}>
# 3. Run the PoC (stdlib-only Python 3):
python3 poc.py --url http://target.example/form-page/
Ожидаемый результат:
[+] field 3 reflects into the confirmation message
[payload (bypass position)] occurrences: LIVE=1 ESCAPED/INERT=2
LIVE -> ...<input onmouseover=alert(poc93485)>...
[control] parent-CVE payload escaped everywhere: YES (fix works at value positions)
=== VERDICT: VULNERABLE — payload reaches attribute-NAME position unescaped ===
poc.py автоматически обнаруживает форму, поля и отражающее поле; отправляет данные анонимно и
классифицирует каждое вхождение как LIVE (инъекция в имя атрибута) или ESCAPED (значение в кавычках,
форма родительской CVE). Код выхода 0 = уязвимо.
Сообщение подтверждения, созданное администратором формы, должно содержать голый отражающий ввод Smart Tag внутри разметки в позиции имени атрибута. Исследователи Patchstack оценили это как нереалистичный шаблон авторинга; тем не менее это документированная позиция функции, экранирование существует именно для того, чтобы подстановка в контексте разметки была безопасной, а автор не получает ошибки — только молчаливую инъекцию.
Кодировать = в экранировании контекста разметки или контекстно разбирать разметку подтверждения
и разрешать подстановку Smart Tag только в текстовых позициях и позициях значений атрибутов в кавычках.
poc.py — самодостаточный PoC (без серверных шагов, без зависимостей)poc.sh — минимальный вариант на основе curlТолько для авторизованного тестирования и образовательных целей. Автор отчёта публикует это после того, как попытка координированной отправки была отклонена; техническая находка не оспаривалась.
Ashish Kunwar (@D0rkerdevil)