
Python PoC для CVE-2026-94504 — неаутентифицированный хранимый XSS в Ninja Forms <= 3.15.3. Определяет уязвимые версии и внедряет полезную нагрузку через nf_ajax_submit.
Ninja Forms — неаутентифицированный stored XSS POC
Ninja Forms ≤ 3.15.3 — неаутентифицированный stored XSS. Публичная текстовая область без RTE сохраняется, затем html_entity_decode()'ится и записывается в устаревший редактор отправок без esc_textarea(). 3.15.4 экранирует это. Для внедрения полезной нагрузки вход не требуется. Она срабатывает, когда редактор открывает эту отправку.
Только для авторизованного тестирования. Не запускайте это против систем, которыми вы не владеете и на тестирование которых у вас нет разрешения.
Требуется: Python 3, только стандартная библиотека.
python3 cve-2026-94504-ninja-forms-stored-xss.py -u http://TARGET --test
python3 cve-2026-94504-ninja-forms-stored-xss.py -u http://TARGET --plant
python3 cve-2026-94504-ninja-forms-stored-xss.py -u https://TARGET --test --insecure
python3 cve-2026-94504-ninja-forms-stored-xss.py -u TARGET --test
| Флаг | Что делает |
|---|
-u / --url | Базовый URL WordPress (обязателен). http:// или https://. Имя хоста без схемы сначала пробуется как HTTPS, затем HTTP. Редиректы http→https сохраняют POST. Плохие TLS-сертификаты повторяются без проверки; --insecure пропускает проверку с самого начала |
--test | Только фингерпринтинг. Читает readme.txt, находит публичную текстовую область без RTE. Не отправляет |
--plant | Неаутентифицированный nf_ajax_submit. Заполняет обязательные поля, помещает полезную нагрузку в текстовую область и выводит URL редактирования. Не открывает браузер |
--page | Страница, на которой размещена форма. По умолчанию сканирует /, /contact/ и список страниц/записей REST |
--field | ID поля текстовой области. По умолчанию — первая текстовая область без RTE |
--payload | Значение текстовой области. По умолчанию закрывает административный <textarea> с помощью img onerror. В cmd.exe не заключайте это в одинарные кавычки (< — это перенаправление). Опустите --payload или используйте двойные кавычки и одинарные внутри alert: --payload "</textarea>" |
--name / --email | Заполнители для других обязательных полей (по умолчанию Ada Lovelace / [email protected]) |
--insecure | Пропустить проверку TLS |
--timeout | Таймаут HTTP в секундах (по умолчанию 30) |
--test читает /wp-content/plugins/ninja-forms/readme.txt (Stable tag) и публичную страницу, чей JSON nfForms содержит "type":"textarea" с отключённым редактором форматированного текста. ≤ 3.15.3 плюс это поле — VULNERABLE. ≥ 3.15.4 — NOT_VULNERABLE. Отсутствие формы на сайте — NEED_FORM.
--plant отправляет POST на /wp-admin/admin-ajax.php с action=nf_ajax_submit и ajaxNonce страницы. Без учётной записи WordPress. При успехе выводит PAYLOAD, SUB и EDIT /wp-admin/post.php?post=ID&action=edit. Скрипт срабатывает, когда эта отправка открывается в wp-admin. На публичной странице формы — нет.
Абзацы форматированного текста (textarea_rte) — это другой путь, и они пропускаются. Поле "Message" формы Contact Me по умолчанию — обычная цель.
Stdout: VERSION, PAGE, FORM, FIELD, PRESENT, VULNERABLE, NOT_VULNERABLE, NOT_FOUND, NEED_FORM, NEED_NONCE, PLANTED, PAYLOAD, SUB, EDIT.
CVE: CVE-2026-94504. Плагин: ninja-forms. Исправлено в 3.15.4. Благодарность: Hippolyte Quéré (Hippie); в changelog 3.15.4 также указан Venkateswara Reddy Challa за исправление экранирования в админке.

