
Набор инструментов для обнаружения и воспроизводимая лаборатория для CVE-2026-87902 — неаутентифицированного обхода пути в WordPress. Включает удалённый чекер, анализатор IoC, правила Sigma и тестовый стенд Docker.
check/ | Удалённый контроллер, пассивный, без доступа к серверу |
detect/ | Анализатор IoC + правила Sigma |
offensive/ | Генератор трасс для проверки обнаружения на реальных журналах |
docker-compose.yml + provision/ | Стенд, три конфигурации |
tests/validate.py | Контроль качества — условие любой публикации |
docs/ANALYSIS.md | Уязвимость, исправление, измеренный анализ достижимости |
make up # monte le banc make ioc # corpus d'attaque -> journaux -> détection
make scan # contrôle le banc make test # porte de qualité
Три экземпляра WordPress на 127.0.0.1, изолирующие каждый фактор вердикта.
| Порт | Экземпляр | Ядро | Активная тема | Ожидаемый вердикт |
|---|---|---|---|---|
| 8091 | vuln-pre | 6.8.1 — не исправлено | Twenty Twelve, с page-templates/ | AFFECTED_PRECONDITION_MET |
| 8092 | vuln-nopre | 6.8.1 — не исправлено | Twenty Twenty-Four, без page-* | AFFECTED_CORE_ONLY |
| 8093 | patched | 6.8.10 — исправлено | Twenty Twelve, с page-templates/ | NOT_AFFECTED |
8092 — самый показательный: то же уязвимое ядро, что и 8091, но отсутствует
предварительное условие темы. Именно это показывает, что триаж, основанный только
на версии, завышает оценку подверженности. Тема — единственная переменная между
8091 и 8092, исправление — единственная между 8091 и 8093: все три содержат
один и тот же тип записи (provision/mu-plugins/00-lab-cpt.php) и один и тот же
зонд состояния запроса (10-lab-debug.php, заголовки X-Lab-*, раскрывающие
is_page(), значение pagename, видимое загрузчику, и итоговый включённый шаблон).
make up # démarre et provisionne — idempotent, relançable
make status # version de chaque instance
make down # arrêt make clean : supprime aussi les volumes
Официальный образ Docker направляет /var/log/apache2/access.log на
/dev/stdout: журналы выводятся в поток контейнера, а не в файл.
docker compose logs --no-log-prefix vuln-pre # accès + erreurs
docker compose logs --no-log-prefix vuln-pre > access.log # pour analyse
docker compose logs -f --no-log-prefix vuln-pre # en direct
make logs # les trois instances
На обычном сервере: /var/log/apache2/access.log, /var/log/nginx/access.log
или /home/*/logs/ у большинства хостинг-провайдеров. Формат должен включать
строку запроса — %r или формат combined её включают, LogFormat, построенный
на %U, её теряет, а без неё обнаружение невозможно.
check/wp-ghsa-7hp8-check.pyИз интернета, без доступа к серверу. Никакой нагрузки, никакого traversal,
никакой записи. Для каждого хоста: обнаружение WordPress, версия, сверенная по
пяти источникам (meta generator, RSS-лента, wp-links-opml.php, readme.html,
?ver= ресурсов ядра), активная тема и зондирование каталога page-*.
python3 check/wp-ghsa-7hp8-check.py --hosts-file hosts.txt --csv out.csv --json out.json
| Вердикт | Значение |
|---|---|
AFFECTED_PRECONDITION_MET | Уязвимое ядро и каталог page-* в активной теме. Приоритетно. |
AFFECTED_THEME_UNKNOWN | Уязвимое ядро, тема не определена. |
AFFECTED_CORE_ONLY | Уязвимое ядро, предварительное условие темы отсутствует. Всё равно исправить. |
VERSION_UNKNOWN | WordPress обнаружен, версия скрыта. |
NOT_AFFECTED | Версия ≥ исправления для своей ветки. |
«ПОДВЕРЖЕН» означает, что уязвимый код присутствует, а не что атакующий может
выполнить код. См. docs/ANALYSIS.md.
--transport browser (по умолчанию) управляет установленным Google Chrome;
подзапросы исходят из fetch(), выполняемого внутри страницы, и наследуют её
стек TLS, порядок заголовков HTTP/2 и cookies, что позволяет избежать фильтрации
CDN до того, как URL будет прочитан. --transport direct использует только
стандартную библиотеку. --scheme http|https избегает отката https → http,
который иначе оставляет строку 400 с сырым ClientHello TLS в журнале цели.
Каждый запрос фиксируется с точностью до миллисекунды в журнале JSONL:
идентификатор сессии, номер запроса, фаза, URL, статус, размер, длительность,
исходящий IP, маркер. Маркер SECAUDIT/<nonce> отправляется в заголовке
X-Security-Audit и в суффиксе User-Agent — добавляется, никогда не
подменяется, чтобы оставаться видимым в стандартном access log, не нарушая
сигнатуру браузера. Настраивается через --marker.
Никакого удалённого оракула. Опция
--probe-inclusionвыполняет дифференциальное сравнение на инертной цели (wp-includes/version.php, уже загруженный при bootstrap:require_onceсделал бы его полным no-op). На стандартной установке она возвращаетNOT_REACHABLEв том числе на уязвимом ядре, с ответами, идентичными до байта между исправленным и неисправленным экземпляром. Это не ограничение инструмента: WordPress отвечает 404 до обращения к иерархии шаблонов. Численная демонстрация вdocs/ANALYSIS.md, раздел 3.
Литеральная регулярка вида pagename=.*%2e%2e%2f обходится сменой регистра
(%2E), дополнительным кодированием (%252e) или смешением литерального и
закодированного (templates/..%2f../). Любой список шаблонов неполон по
построению.
Поэтому исходим из кода, а не из написания атакующего:
pagename подвергается не более чем двум декодированиям до достижения
диска — декодированию PHP в строке запроса, затем явному urldecode() в
get_page_template().file_exists(), должен
содержать компонент ... В Linux родительский каталог записывается ровно
двумя байтами 0x2E 0x2E; никакого другого представления на уровне
файловой системы не существует.Итак: декодировать до неподвижной точки и проверять на каждом уровне. Это строгое надмножество того, что делает WordPress — добавление слоя кодирования лишь смещает соответствие на уровень, который мы также проходим.
python3 detect/wp-ghsa-7hp8-ioc.py access.log
docker compose logs --no-log-prefix vuln-pre | python3 detect/wp-ghsa-7hp8-ioc.py -
| Правило | Критичность | Триггер |
|---|---|---|
GHSA-7hp8-traversal-pagename | CRITICAL | компонент .. в pagename, на любом уровне декодирования |
GHSA-7hp8-traversal-param | HIGH | та же примитива в другом параметре (темы и расширения также вызывают locate_template()) |
GHSA-7hp8-traversal-path | HIGH | компонент .. в пути URL (nginx пропускает %2f, Apache по умолчанию — нет) |
GHSA-7hp8-overlong-encoding | MEDIUM | перекодирование UTF-8 (%c0%ae). Не работает в Linux, но никогда не легитимно |
GHSA-7hp8-theme-page-dir-probe | LOW | зондирование каталога page-* темы — разведка |
Правила Sigma в detect/sigma-wp-ghsa-7hp8.yml.
Поскольку Sigma не умеет рекурсивно декодировать, они перечисляют уровни с 0 по 3:
это осознанное приближение для первичной сортировки. Совпадения следует
перепроверять анализатором для принятия решения.
WP::parse_request() читает $_POST до $_GET. Поэтому pagename
может прийти в теле запроса, отсутствуя в любом access log. Необходимо покрытие
на уровне WAF или ModSecurity, по телу.Ни одно правило по access log не покрывает первый пункт. Это ограничение носителя, а не правила — но о нём нужно знать, прежде чем объявлять полное покрытие.
offensive/generate-traces.py воспроизводит 12 различных написаний одной и той
же нагрузки (литеральное, одинарное/двойное/тройное кодирование, верхний и
нижний регистр, смешения, перекодирование UTF-8, точка-пробел) плюс 7 легитимных
запросов, похожих на них (slug с точками, wp-includes в slug, постоянная ссылка
с датой, закодированный процент).
Он ничего не получает: удалённого эксплойта для этого вектора на стандартной установке не существует. Он создаёт трассы — в этом вся его цель.
make ioc # génère le corpus, récupère les journaux réels, passe l'analyseur
Ожидается и проверяется через make test на реальных журналах Apache:
12 нагрузок обнаружено из 12 (11 CRITICAL, перекодирование — MEDIUM, так как
оно не эксплуатируемо в Linux), 0 срабатываний на 7 легитимных запросах и
фактическое обнаружение на трёх различных уровнях декодирования.
Цель ограничена локальным стендом; любая другая требует --i-have-authorization.
register_argc_argv = Off в php.ini веб-SAPI; удалить PEAR, если не
используется — это упомянутый в бюллетене стык включение → выполнение.open_basedir, ограниченный корнем сайта: локализует любое локальное включение.make test — условие публикации: матрица из 25 веток бюллетеня (включая ловушки
сравнения — 6.8.9 < 6.8.10 численно, предрелизы, ветки вне матрицы), контроллер
против трёх экземпляров и правило IoC, проверенное на реальных журналах.
Утверждение NOT_REACHABLE зонда намеренно зафиксировано: если оно сломается,
значит изменилось поведение и анализ нужно пересмотреть.
Использовать только на активах, за которые вы отвечаете, или по письменному
мандату. Стенд слушает только на 127.0.0.1; уязвимые экземпляры никогда не
должны быть открыты наружу. Зонд 10-lab-debug.php раскрывает серверные пути:
он предназначен только для стенда.