
PoC для CVE-2026-87902 — неаутентифицированный обход пути в разрешении шаблонов страниц WordPress (локальное включение PHP, условное RCE) с закреплённой уязвимой лабораторией
Неаутентифицированное локальное включение PHP-файла в WordPress Core через дважды закодированное
значение pagename — и, при определённых условиях развёртывания, выполнение PHP-кода
с привилегиями учётной записи веб-сервера.
| CVE | CVE-2026-87902 |
| Уведомление вендора | GHSA-7hp8-65ch-5whp |
| Разбор | CVE-2026-87902: Critical WordPress file inclusion and conditional RCE |
| Затронуто | WordPress Core 4.7.0 – 7.1.1 (все ветки, согласно диапазонам по веткам в уведомлении); динамически воспроизведено на 7.0.2 |
| Исправлено | 7.1.2 (ветка 7.1), 7.0.6 (ветка 7.0) и бэкпорт для каждой ветки вплоть до 4.7.37 (согласно уведомлению) |
| Слабости | CWE-98 (ненадлежащий контроль имени файла при include), CWE-22 / CWE-23 (обход пути) |
| CVSS v3.1 | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — 8.1 High |
| CVSS v4.0 (дополнительно) | CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N — 9.2 Critical |
| Аутентификация | отсутствует — без учётной записи, cookie, nonce, сессии, плагина или исходящего запроса |
| Взаимодействие с пользователем | отсутствует |
| Автор | Robert Ressl — ressl.ch |
| PoC проверен | 2026-09-22 на лабораторном стенде в этом репозитории — см. Проверенные результаты |
WordPress разрешает шаблон страницы через цепочку, которая нигде не доказывает, что выбранный файл остаётся внутри корня темы:
pagename и page_id — публичные переменные запроса, которые WP::parse_request()
принимает из анонимного тела POST.pagename переживает раннюю санитизацию
запроса как экранированные октеты %xx (wp_basename() не видит %2f как
разделитель, а sanitize_title_with_dashes() намеренно сохраняет корректные
октеты).page_id выбирает реальную опубликованную страницу, тогда как вредоносный
pagename остаётся в объекте запроса.get_page_template() вызывает urldecode() для этого значения и добавляет
кандидата, такого как page-templates/../../../../../../../usr/local/lib/php/pearcmd.php,
в иерархию шаблонов.locate_template() и финальный загрузчик шаблона проверяют только существование,
читаемость и суффикс .php/.html — но никогда то, что канонический путь
по-прежнему находится внутри разрешённого корня темы — и затем выполняет include.Это неаутентифицированный примитив удалённого локального включения файла в WordPress
Core. На протестированной официальной среде выполнения (wordpress:php8.3-apache, которая включает
PEAR и не загружает php.ini, поэтому register_argc_argv имеет значение On), включение было
сцеплено через pearcmd.php из PEAR: первый анонимный запрос заставляет
config-create записать контролируемый атакующим PHP-код в /tmp, второй анонимный
запрос включает этот файл и выполняет его от имени www-data.
Отсутствие ограничения пути в Core и есть уязвимость. PEAR — лишь один зависящий от окружения маршрут от включения к выполнению кода — это не зависимость WordPress, и он присутствует или пригоден не во всех развёртываниях.
Проверенные местоположения в исходном коде (WordPress 7.0.2):
| # | Расположение | Роль |
|---|---|---|
| 1 | wp-includes/class-wp.php:18,322-330 | pagename и page_id — публичные переменные запроса, и они читаются из $_POST |
| 2 | wp-includes/class-wp-query.php:2205 | sanitize_title_for_query( wp_basename( $query_vars['pagename'] ) ) — %2f не является разделителем для wp_basename() |
| 3 | wp-includes/formatting.php:2283-2289 | sanitize_title_with_dashes() сохраняет корректные октеты %xx вместо их удаления |
| 4 | wp-includes/template.php:492 | $pagename_decoded = urldecode( $pagename ); — обход пути становится активным после санитизации |
| 5 | wp-includes/template.php:722-736 | locate_template() конкатенирует кандидата под каждым корнем темы и вызывает только file_exists() |
| 6 | wp-includes/template-loader.php:116-132 | realpath() нормализует путь, затем include выполняется без канонической проверки нахождения внутри корня |
| 7 | wp-includes/canonical.php:42-47 | redirect_canonical() завершается досрочно для не-GET/HEAD, поэтому POST не канонизируется и не отбрасывается |
Автономному примитиву включения требуется:
| # | Предусловие | Причина |
|---|---|---|
| 1 | Опубликованная, анонимно доступная страница, выбранная числовым page_id | страница должна быть возвращена запросом после неудачи поиска по имени пути |
| 2 | Отсутствие более раннего разрешаемого пользовательского шаблона страницы | корректный назначенный шаблон упорядочен перед вредоносным кандидатом |
| 3 | Каталог верхнего уровня в активной (дочерней или родительской) теме, имя которого начинается с page-, например page-templates/ | WordPress добавляет фиксированный префикс page-, поэтому обход .. не может начаться с позиции 0; каталог должен лишь существовать и быть проходимым — он не обязан быть доступным для записи |
| 4 | Выбранный локальный .php-файл, который существует и читаем пользователем PHP | загрузчик проверяет is_file()/is_readable() и требует суффикс .php |
| 5 | Отсутствие ограничения файловой системы, блокирующего этот файл | open_basedir или политика MAC могут предотвратить включение |
Продемонстрированному этапу с PEAR дополнительно нужны читаемый pearcmd.php (вместе с его
зависимостями), register_argc_argv=On для веб-SAPI и доступный для записи выходной
каталог. В производственных файлах php.ini устанавливается register_argc_argv=Off; протестированный
образ не загружает php.ini, поэтому применялось скомпилированное значение по умолчанию (On). Это
важное ограничение на распространённость продемонстрированной цепочки выполнения кода.
Поставляемые темы Twenty Twenty-Three/Four/Five не содержат каталога page-*
верхнего уровня, поэтому стандартному лабораторному стенду нужна фикстура, описанная ниже. Пользовательские темы могут
законно использовать структуру page-templates/
(документация WordPress).
В уведомлении зафиксировано, где эти условия встречаются в поставляемом ПО: условие темы
выполняется для устаревших тем Twenty Twelve и Twenty Fourteen и для
сторонних тем, таких как Neve, Hestia и Sydney, тогда как переход через PEAR
применим к официальному Docker-образу PHP и к конфигурациям cPanel по умолчанию,
работающим на PHP старше 8.5. (Twenty Twelve и Twenty Fourteen обе поставляются с
каталогом page-templates/ верхнего уровня; остальные утверждения принадлежат
уведомлению.) Этот репозиторий не измеряет, как часто применима полная цепочка.
./lab/up.sh # WordPress 7.0.2 + MySQL 8.4 + the page-* fixture, installed and ready
python3 cve-2026-87902.py # two anonymous POSTs, prints the proof marker
cve-2026-87902.py требует Python 3.6+ (только стандартная библиотека) и по умолчанию обращается к лабораторному стенду по
http://127.0.0.1:8091. Ожидаемый вывод: