Статья Grav CMS уязвимость path traversal: 0-day в FormFlash без аутентификации

Криминалистический стол с разобранным одноплатным компьютером и картой памяти. На экране ноутбука — перехваченный POST-запрос с выделенной строкой обхода пути в красном цвете.


CVSS 8.8 (HIGH), ноль привилегий, автоматизируемая эксплуатация - по оценке CISA-ADP, Technical Impact: total. CVE-2026-42608 в Grav CMS - path traversal через компонент FormFlash, где единственный POST-параметр __form-flash-id превращает любую страницу с формой в точку входа. Неаутентифицированный атакующий создаёт произвольные директории и пишет YAML-файлы с контролируемым содержимым прямо в каталоги конфигурации. В русскоязычном сегменте детальный разбор кода этой уязвимости до сих пор не публиковался - закрываю пробел: от уязвимой строки PHP до рабочего HTTP-запроса, с контекстом предыдущих CVE Grav и маппингом на MITRE ATT&CK.

FormFlash: уязвимый компонент Grav CMS и поверхность атаки​

Grav - flat-file CMS на PHP с 14 500+ звёзд на GitHub и свыше 100 000 загрузок Docker-образа (по данным TantoSec). Вместо базы данных Grav хранит всё в YAML-файлах: конфигурация сайта, учётки пользователей, контент страниц, настройки плагинов - всё лежит на диске. Для понимания импакта path traversal это принципиально: любая запись файла в произвольный каталог - это модификация поведения приложения. Не «потенциальная», а прямая.

FormFlash (Grav\Framework\Form\FormFlash) - внутренний компонент, отвечающий за сохранение данных форм между HTTP-редиректами. Стандартный сценарий: посетитель заполняет контактную форму, шлёт POST - Grav делает redirect (302) на страницу подтверждения, а FormFlash между двумя запросами сбрасывает промежуточные данные во временную директорию на диске. После обработки - удаляет.

Критическая деталь: FormFlash принимает идентификатор сессии напрямую из POST-параметра __form-flash-id. Именно этот параметр определяет путь к директории записи. Компонент доступен на любой странице с Grav Forms: контактная форма, /admin/login, регистрация, любая кастомная форма. Аутентификация не нужна - публичный endpoint.

Поверхность атаки определяется тем, что большинство инсталляций Grav с плагином Administrator содержат минимум одну форму. По данным TantoSec, ZoomEye находит порядка 36 000 инстансов по запросу «Grav Admin Login» - каждый из них потенциально уязвим.

Как FormFlash строит путь хранения​

Метод __construct() класса FormFlash получает session_id из POST-параметра и использует его при построении пути. Ключевой фрагмент (по данным advisory GHSA-hmcx-ch82-3fv2):
PHP:
$folder = $config['folder']
    ?? ($this->sessionId
        ? 'tmp://forms/' . $this->sessionId
        : '');
$this->folder = $folder && $locator->isStream($folder)
    ? $locator->findResource($folder, true, true)
    : $folder;
$this->sessionId - значение __form-flash-id из POST-запроса. Оно конкатенируется напрямую со строкой 'tmp://forms/' без какой-либо проверки на управляющие символы пути. Дальше findResource() разрешает stream-URI в реальный путь файловой системы. Если sessionId содержит последовательности ../, разрешённый путь выходит за границы tmp/forms/ и попадает в произвольные каталоги webroot.

Причина - отсутствие вызова basename() или regex-валидации перед конкатенацией. Разработчики обращались с session_id как с доверенным значением: «ну строка из POST, она же не в SQL попадает - какой вред?». В flat-file архитектуре ответ другой.

Path traversal без аутентификации: анатомия CVE-2026-42608​

Уязвимость классифицирована как CWE-22 (Improper Limitation of a Pathname to a Restricted Directory). Разберу CVSS-вектор по компонентам - здесь CVSS 4.0:
  • AV:N - атака через сеть, стандартный HTTP POST
  • AC:L - сложность низкая, специальных условий нет
  • AT:N - не требует предварительных действий
  • PR:N - привилегии не нужны (unauthenticated)
  • UI:N - взаимодействие пользователя не требуется
  • VC:H / VI:H - высокое влияние на конфиденциальность и целостность
  • VA:N - прямого влияния на доступность нет (хотя через inode exhaustion возможен DoS)
  • E:P - есть proof-of-concept
CISA SSVC оценила: Exploitation: poc, Automatable: yes, Technical Impact: total. Решение - Track* (следить, готовить патч).

Что записывается на диск​

При успешной эксплуатации FormFlash создаёт:
  1. Произвольную директорию по пути, заданному через traversal-последовательность
  2. Файл index.yaml внутри этой директории с данными, частично контролируемыми атакующим
Содержимое index.yaml формируется из данных формы: поля form, id, unique_id и пользовательских данных из POST-параметров form-data[*]. Атакующий контролирует и путь записи, и часть содержимого файла - в рамках YAML-формата. Это уже Configuration Injection.

Каталоги Grav CMS в зоне доступа​

Поскольку Grav хранит всё в файловой системе, traversal открывает доступ к критичным каталогам:

КаталогСодержимоеИмпакт при записи
user/config/Конфигурация сайта, плагинов, темConfiguration Injection - изменение поведения приложения
user/accounts/YAML с хешами паролей, 2FA-секретами, токенами сбросаПодмена/порча учётных данных
user/pages/Контент страниц в Markdown/TwigИнъекция контента, потенциальный SSTI
cache/Кеш шаблонов и маршрутовИнвалидация кеша, инъекция в скомпилированные шаблоны
tmp/Временные файлыМежсессионная коррупция данных

Запись в user/config/ - самый жирный вектор: Grav загружает конфигурацию из YAML-файлов при каждом запросе. Инъекция настроек в подкаталог плагина может изменить его поведение - от включения отладочного режима до модификации маршрутов. По классификации OWASP Top 10 это одновременно A01:2021 (Broken Access Control - нет проверки аутентификации для критичной операции записи) и A03:2021 (Injection - traversal как инъекция в путь файловой системы).

Grav CMS эксплойт: воспроизведение уязвимости пошагово​

Требования к окружению​

  • ОС: любая с поддержкой Docker (Linux, macOS, Windows с WSL2)
  • RAM: минимум 512 МБ для контейнера Grav
  • Grav CMS: v1.7.49.5 - последний стабильный релиз на момент обнаружения уязвимости (уязвим). Рекомендуется официальный Docker-образ
  • Инструменты: Burp Suite, curl или любой HTTP-клиент с возможностью ручного формирования POST-тела
  • Обязательное условие: наличие хотя бы одной страницы с Grav Form (стандартная установка с плагином Administrator содержит /admin/login)
  • Сеть: HTTP-доступ к целевому инстансу
Шаг 1.
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

Эксплуатация воспроизводима на 100% - детерминистически, без race condition и без временных окон. По данным advisory GHSA-hmcx-ch82-3fv2, надёжность подтверждена на v1.7.49.5 (latest stable) и на текущей ветке разработки (март 2026).

Цепочка атаки: от path traversal до unauthenticated RCE в Grav​

CVE-2026-42608 сам по себе даёт создание директорий и запись YAML-файлов. Прямого выполнения кода нет. Но в контексте полной цепочки атаки на Grav CMS эта уязвимость становится звеном, которое серьёзно снижает порог входа - Exploit Public-Facing Application (T1190, Initial Access по MITRE ATT&CK).

Три вектора развития атаки​

Configuration Injection. Grav динамически загружает конфигурацию из user/config/. Запись index.yaml в подкаталог плагина или темы может изменить настройки маршрутизации, обработки шаблонов, авторизации. В flat-file архитектуре это эквивалент SQL-инъекции в таблицу конфигурации - только через файловую систему. Звучит экзотично, но работает так же надёжно.

Cross-User Session Corruption. Контролируя session_id, атакующий перезаписывает временные данные форм других пользователей - ломает изоляцию сессий. Если администратор заполняет форму в панели управления, атакующий может подменить его промежуточные данные.

Denial of Service через inode exhaustion. Каждый POST-запрос создаёт новую директорию. На файловых системах с лимитом inodes (ext4 с дефолтными настройками) массовая отправка запросов исчерпает inodes - сервер перестанет создавать файлы. Для shared-хостингов это особенно больно.

Историческая цепочка CVE в Grav CMS​

У Grav CMS задокументированная история path traversal и связанных уязвимостей. В совокупности они формируют цепочку от unauthenticated access до полного RCE (по данным исследования TantoSec):

CVE-2024-27921 (CVSS 8.8 HIGH, CWE-22) - path traversal при загрузке файлов в версиях до 1.7.45. Аутентифицированный пользователь с низкими привилегиями может заменять или создавать файлы с расширениями .json, .zip, .css, .gif. EPSS: 0.0879 - входит в Top 10% по вероятности эксплуатации (92-й перцентиль), что намекает на реальное использование. CISA-ADP: Automatable: no, Technical Impact: total.

CVE-2024-34082 (CVSS 8.5 HIGH, CWE-269 - Improper Privilege Management) - чтение произвольных серверных файлов через Twig-синтаксис в версиях до 1.7.46. Пользователь с правами редактирования страниц читает user/accounts/*.yaml, где лежат хеши паролей, секреты 2FA и токены сброса. CISA-ADP: Technical Impact: total.

Цепочка эксплуатации, описанная TantoSec: Password Reset Poisoning (через Host header на /admin/forgot) → захват учётной записи администратора → CVE-2024-34082 (чтение учётных данных через SSTI) → CVE-2024-27921 (загрузка файлов через path traversal) → злоупотребление Scheduler для выполнения кода на сервере.

CVE-2026-42608 добавляет новый вектор в начало этой цепочки: он работает полностью без аутентификации и не требует SMTP (в отличие от Password Reset Poisoning). Там, где почтовый сервер не настроен, именно CVE-2026-42608 даёт альтернативную точку входа для модификации файловой системы.

Маппинг на MITRE ATT&CK​

ЭтапТехникаIDКонтекст в Grav
Initial AccessExploit Public-Facing ApplicationT1190CVE-2026-42608: FormFlash path traversal
DiscoveryFile and Directory DiscoveryT1083Перечисление структуры через traversal
CollectionData from Local SystemT1005Чтение YAML-конфигов и учёток (CVE-2024-34082)
PersistenceWeb ShellT1505.003Запись файлов в webroot через цепочку уязвимостей
Privilege EscalationValid AccountsT1078Захват учётных данных из YAML-файлов
ExecutionUnix ShellT1059.004RCE через Scheduler при admin-доступе

Цепочка проходит от Initial Access (T1190) через Discovery и Collection до Persistence и Execution - шесть тактик ATT&CK одним набором уязвимостей в одной CMS.

Ограничения path traversal в Grav CMS и возможности детекта​

Когда эксплуатация CVE-2026-42608 не работает​

Ограничение прав web-сервера. Если PHP/Apache/Nginx запущен с минимальными привилегиями, а user/config/ и user/accounts/ принадлежат root с permissions 0750 - запись не пройдёт. На практике большинство установок через Docker-образ используют www-data как владельца всей webroot, и никаких ограничений нет. Hardened-инсталляции на bare metal с разделением привилегий - исключение, не правило.

Отсутствие форм. Grav как статический генератор без плагина Admin и без пользовательских форм не предоставляет поверхности атаки. Конфигурация нетипичная - по данным TantoSec, большинство production-инсталляций используют Administrator plugin.

Reverse proxy с URL-нормализацией. Nginx с merge_slashes on или Cloudflare с WAF может нормализовать или отклонить запросы с ../ в теле. Но параметр __form-flash-id передаётся в POST body (не в URL), и далеко не все WAF инспектируют POST-параметры с одинаковой глубиной. Тут как повезёт.

Подходы к детекту​

На уровне WAF. Мониторинг POST-параметров на последовательности ../ и их кодированные варианты (..%2f, %2e%2e/) в параметре __form-flash-id. ModSecurity CRS правило 930100 покрывает базовый случай, но для Grav эффективнее таргетированное правило на конкретный параметр - меньше false positive от легитимных форм.

На уровне файловой системы. Настроить auditd или inotify на мониторинг создания файлов в user/config/, user/accounts/, user/pages/. Появление index.yaml в неожиданных подкаталогах - прямой IoC. В нормальной работе Grav формы пишут только в tmp/forms/<session_uuid>/.

На уровне логов. POST-запросы с __form-flash-id, содержащим символы /, \ или .. - аномалия. В штатной работе этот параметр содержит UUID сессии (строго алфавитно-цифровую строку). Простой grep по access-логам с регулярным выражением на traversal-паттерны в POST-данных выявляет попытки эксплуатации.

Патч и Grav CMS безопасность: как закрыли CVE-2026-42608​

Исправление применено в ветке Grav 2.0 - коммит d904efc33, включённый в релиз 2.0.0-beta.2 (по данным advisory GHSA-hmcx-ch82-3fv2, дата фикса - 2026-04-24).

Суть патча: FormFlash::[B]construct() теперь валидирует session_id, unique_id и id через строгий allowlist - регулярное выражение [A-Za-z0-9,_-]{1,64}. Значения, не прошедшие валидацию, сбрасываются в пустую строку. При пустом идентификаторе методы save(), delete() и getTmpDir() становятся no-op: POST-запрос с [/B]form-flash-id=../../user/config/proof_dir просто ничего не создаёт на диске. Один regex - и уязвимости нет.

Патч сопровождается 32 unit-тестами в файле FormFlashSecurityTest.php, покрывающими оригинальный PoC и его вариации (двойное кодирование, альтернативные разделители, edge cases длины). Тут разработчики молодцы - закрыли не только дырку, но и все очевидные обходы.

Защитный чеклист​

  1. Обновить Grav до 2.0.0-beta.2 или более поздней версии. Для ветки 1.7.x патч не портирован - если обновление невозможно, применить workaround через WAF
  2. Права файловой системы: выставить user/config/ и user/accounts/ в chmod 0750 с владельцем, отличным от процесса web-сервера (где архитектура позволяет)
  3. WAF-правило: блокировать POST-запросы с ../, ..%2f, %2e%2e/ в параметре __form-flash-id
  4. Мониторинг: auditd/inotify на создание файлов в user/config/ вне стандартной административной активности
  5. Минимизация поверхности: если формы на публичных страницах не используются - отключить плагин Form или ограничить его аутентифицированными пользователями через конфигурацию маршрутов
В CVE-2026-42608 меня занимает не техническая сторона (она тривиальна - один вызов basename() или regex решает проблему), а устойчивый паттерн, который я вижу при аудите PHP-кода flat-file CMS. Разработчики систематически обращаются с идентификаторами сессий как с безопасными значениями: «строка из cookie/POST, она не попадает в SQL - какой от неё вред?». В flat-file архитектуре ответ другой: любой пользовательский ввод, участвующий в конструкции пути файловой системы - прямой вектор для path traversal. Grav не уникален: тот же паттерн встречается в других CMS с файловым хранилищем, просто у Grav уязвимый код оказался на unauthenticated endpoint с формами на каждом инстансе.

EPSS для CVE-2026-42608 пока составляет 0.0012 (30-й перцентиль) - формально низкая вероятность массовой эксплуатации. Но CISA-ADP маркирует уязвимость как Automatable: yes - а значит, массовое сканирование через Shodan/ZoomEye по сигнатуре «Grav Admin Login» с автоматической отправкой PoC-запроса к каждому из 36 000 инстансов это вопрос не «если», а «когда». Пока EPSS не догнал реальность, окно для патча ещё открыто. Если интересно посмотреть как подобный path traversal ломается на живом стенде - лаба по web-эксплуатации на HackerLab.pro даёт именно этот формат.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab