ModSecurity разбирает составные запросы загрузки файлов иначе, чем приложение за ним. Фильтр смотрит на одно имя файла, а бэкенд берёт другое. Расширение, которое прошло проверку, и расширение, с которым файл ложится на диск, могут не совпадать. Затронуты приложения с загрузкой файлов за ModSecurity, написанные на Go, Python, Node.js или Java.
Расхождение в разборе заголовка
Причина - параметр filename*. RFC 2231 описывает расширенный синтаксис параметров, где имя файла передают закодированным вместе с указанием кодировки. Разборщики в Go, Python, Node.js и Java этот синтаксис поддерживают. Если в заголовке Content-Disposition есть и обычный filename, и filename*, они выбирают второе значение. ModSecurity такого приоритета не применяет.
Отсюда и обход. Запрос несёт безопасное имя вида safe.jpg в параметре filename и имя shell.php в параметре filename*. Правила OWASP CRS, которые смотрят на расширение, видят первое имя и пропускают загрузку. Приложение получает второе имя и сохраняет файл с ним. В отчёте об уязвимости приведён разбор запроса, где оба слоя принимают решение по разным именам.
Условия атаки простые. Атакующему достаточно отправить составной POST-запрос на форму загрузки. Учётные данные не нужны, участие пользователя тоже. Сложность низкая, атака идёт по сети - так оценка и описана. Если приложение использует полученное имя для сохранения, проверки расширения или определения типа содержимого, защита периметра обходится целиком.
Уязвимость получила идентификатор CVE-2026-104269. Оценка CVSS 3.1 - 8,6 из 10, высокий уровень опасности. Влияние на целостность высокое, на конфиденциальность и доступность - нет. Тип слабости - CWE-436, конфликт интерпретации.
Что меняется после патча
Затронуты версии ModSecurity от 3.0.0 до 3.0.17 не включая. Исправление вышло в релизе 3.0.17. Разборщик научился обрабатывать параметр filename* и повторяющиеся параметры filename. Кроме того, в релизе появились переменные MULTIPART_DUPLICATE_PART_HEADER, MULTIPART_FILENAME_CHARSET и MULTIPART_FILENAME_LANGUAGE - по ним правила могут проверять кодировку и язык имени.
После обновления оба слоя читают одно и то же имя. Правила MULTIPART_FILENAME перестают работать вслепую: если в заголовке есть filename*, разборщик его учитывает. Это меняет и настройку правил - часть проверок имеет смысл дополнить новыми переменными.
Патч закрывает конкретный способ вызвать расхождение, а не саму возможность ошибки в приложении. Собственная проверка имени файла всё равно нужна. Расширение, тип содержимого и путь сохранения лучше определять на стороне приложения, а имена генерировать самостоятельно.
Соседние исправления
Релиз 3.0.17 устраняет и другие уязвимости ModSecurity, не связанные с этой. Среди них обход проверки тела ответа при смешанном регистре в значении Content-Type. В обработчике тела XML-запроса исправлена ошибка работы с неинициализированным указателем. Закрыт обход и при загрузке правил по удалённому источнику - там теперь используют безопасное значение для проверки хоста.
В том же релизе поправили декодирование HTML-сущностей, обработку base64 в URL-безопасном алфавите и работу преобразования removeComments с соседними комментариями. Эти правки стоит учесть тем, кто откладывал обновление.
Насколько это срочно
Код, показывающий эксплуатацию, опубликован. Сведений о применении уязвимости в реальных атаках нет. При этом публичный эксплойт снижает порог входа: повторить запрос сможет любой, кто прочитал отчёт.
Для организаций с формой загрузки файлов за ModSecurity обновление до 3.0.17 - первоочередная задача. Тем, у кого бэкенд на PHP, повезло больше: встроенный разборщик читает только обычный filename, поэтому расхождение не возникает. Остальные приложения стоит проверить по стеку: Go, Python, Node.js и Java подвержены все.