FlawOpen

flawopen.com/Teardowns/cve-2026-87902-wordpress-page-template-path-traversal

● CVE-2026-87902 · CVSS 9.2[1] · Критический
Исследования · FlawOpen

CVE-2026-87902: обход пути в шаблонах страниц WordPress

Непроверенное декодированное имя страницы попало в выбор шаблона, что позволяло подключать локальный PHP-файл без аутентификации; RCE требовало условий темы и PHP.[1]

💡 Простыми словами (ELI5)

Представьте библиотекаря, который проверяет запрос. WordPress проверял один путь к шаблонам, но пропускал путь через декодированное имя; специальные маркеры могли вывести его за пределы темы. Исправление проверяет имя и оставляет шаблон в разрешённом каталоге.[1], [2], [3]

Ключевые понятия и термины

Выбор шаблона страницы
WordPress упорядочивает кандидатов PHP и ищет первый подходящий файл в активной теме.[1], [2]
Обход пути
Компоненты вроде .. могут выйти за пределы каталога, если входные данные не проверены.[1]
Подключение локального PHP
Загрузка уже имеющегося на сервере PHP-файла; RCE требует дополнительных условий.[1]
`validate_file()`
Функция WordPress, отклоняющая недопустимые компоненты пути до их использования.[3]
CWE-98
CWE-98 — Недостаточный контроль имени файла для include/require: входные данные недостаточно ограничиваются до выбора PHP-файла.[1], [7]

Связи со стандартами

Анализ первопричины

В get_page_template() WordPress декодировал pagename и добавлял page-{name}.php без validate_file(), хотя соседняя ветвь выполняла проверку. Путь мог выйти за пределы темы. Нужны каталог верхнего уровня page-* и читаемый локальный PHP-файл; для RCE нужны дополнительные условия PHP/сервера. Версия 7.1.2 проверяет декодированное значение и ограничивает путь в locate_template(). В бюллетене WordPress эта слабость отнесена к CWE-98 (недостаточный контроль имени файла для PHP include/require); автором обнаружения и ответственного раскрытия указан Robert Ressl.[1], [2], [3], [7]

Границы CVE-2026-87902: уязвимость и условное RCE

Условие развертыванияЧто подтверждают первичные источникиДействие оператора
Уязвимая ветка WordPress CoreВ GHSA уязвимые версии указаны отдельно для каждой ветки: от 4.7.0–4.7.36 до 7.1.0–7.1.1. Соответствующие исправления охватывают версии от 4.7.37 (самый ранний backport) до 7.1.2 (текущая ветка); для каждой указанной ветки предусмотрен отдельный исправленный выпуск.Сверьте установленную версию Core с исправлением для соответствующей ветки. Обновите версии ниже 4.7 до поддерживаемого выпуска.
Структура активной темыВ активной родительской или дочерней теме должен быть каталог верхнего уровня, имя которого начинается с page- (например, page-templates).Проверьте каталоги обеих активных тем. Повторно проверьте это условие после смены активной темы.
Доступный локальный PHP-файлВыбранный файл .php должен существовать вне каталогов активных тем и быть доступен для чтения учетной записи веб-сервера.Проверьте, какие локальные PHP-файлы доступны веб-серверу; диапазон версий CVE сам по себе не доказывает включение файла.
Среда для условного RCEВ бюллетене RCE описан только при дополнительных условиях PHP/сервера. Среди примеров — pearcmd.php при register_argc_argv=On, официальный PHP-образ Docker и стандартные конфигурации cPanel с PHP ниже 8.5.Оцените фактическую среду и конфигурацию PHP. Уязвимая версия WordPress сама по себе не доказывает RCE или компрометацию.

Пошаговый сценарий атаки

Шаг 1

Ввод попадает в выбор шаблона

Неаутентифицированный запрос передаёт имя для кандидатов; аккаунт и клик не нужны.[1]

Шаг 2

Повторное декодирование

get_page_template() декодирует имя ещё раз; уязвимые версии не проверяли декодированный кандидат.[2], [3]

Шаг 3

Поиск выходит за корень

При каталоге page-* и читаемом PHP-файле тема может разрешить файл вне своего каталога.[1]

Шаг 4

Условное воздействие

RCE требует дополнительных условий PHP/сервера; версия сама по себе не доказывает эксплуатацию или взлом.[1]

Исходный код: Уязвимый vs Защищённый вариант

Этот фрагмент сравнивает официальные теги 7.1.1 и 7.1.2. Исправление проверяет значение после декодирования; тот же выпуск ограничивает разрешённые пути в locate_template(). Это исходный код upstream, не полный самостоятельный патч и не демонстрация эксплуатации.[2], [3]

Фрагмент исходного кода: wp-includes/template.php — get_page_template()

УЯЗВИМАЯ РЕАЛИЗАЦИЯ
if ( $pagename ) {
    $pagename_decoded = urldecode( $pagename );

    if ( $pagename_decoded !== $pagename ) {
        $templates[] = "page-{$pagename_decoded}.php";
    }
}
БЕЗОПАСНЫЙ ИСПРАВЛЕННЫЙ ВАРИАНТ
if ( $pagename ) {
    $pagename_decoded = urldecode( $pagename );

    if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
        $templates[] = "page-{$pagename_decoded}.php";
    }
}

Чек-лист по защите системы для инженеров

Источники