ModSecurity с OWASP CRS 3.2: неделя detection, потом blocking - и ложноположительных почти не осталось
Развернули ModSecurity с CRS 3.2 перед публичными API: неделя в detection-режиме показала, что ложноположительных в разы меньше, чем с CRS 3.0.
OWASP ModSecurity Core Rule Set 3.2 вышел в 2019 году с улучшенной защитой от инъекций и существенно сниженным числом ложноположительных срабатываний по сравнению с CRS 3.0
В managed-инфраструктуре мы давно используем ModSecurity, но CRS 3.0 в blocking-режиме периодически выдавал такое количество ложноположительных, что заказчики просили его переключить в detection или вообще снять. Типичная история: легитимный JSON с полем description длиной триста символов - и правило 942100 с радостью блокирует запрос как потенциальную SQL-инъекцию. Объяснять клиенту, почему его API не принимает собственный формат данных, всегда весело.
С выходом CRS 3.2 решили поставить эксперимент по-человечески: не просто обновить версию и посмотреть, что сломается, а пройти через detection-фазу с нормальным анализом трафика.
Что изменилось в CRS 3.2
Ключевые правки по сравнению с ветками 3.0/3.1 касаются именно того, что нас раздражало больше всего. Переработана логика paranoia level - теперь уровень 1 реально подходит для продакшна без ручной настройки исключений под каждое приложение. Улучшена работа с JSON-телами запросов: анализ стал точнее, меньше ситуаций, когда валидный JSON триггерит injectoin-правила просто из-за синтаксиса. Переработаны некоторые правила обнаружения XSS - убраны паттерны, которые раньше срабатывали на обычные URL с параметрами.
Кроме этого, в 3.2 добавлена более точная поддержка Content-Type application/json и multipart, что для API-периметра принципиально.
Схема развёртывания
Поставили перед тремя публичными API одного из клиентов: платёжный шлюз, личный кабинет и webhook-эндпоинт от партнёров. Nginx с ModSecurity 2.9, CRS 3.2, paranoia level 1 для старта.
Первая неделя - чистый detection. SecRuleEngine DetectionOnly в конфиге, все сработки пишутся в modsec_audit.log, блокировок нет. Это важно: на CRS 3.0 мы такой фазы не делали, сразу включали blocking с exclusion-листом «по ходу», и это была грустная история длиной в несколько недель.
На detection мы смотрели три вещи:
- Какие правила срабатывают на реальный трафик - не гипотетически, а на живых запросах от живых пользователей и партнёрских систем.
- Соотношение атаки vs. легитимный трафик - есть ли в логах что-то похожее на настоящие попытки, или всё это ложноположительные.
- Правила с высоким числом срабатываний на легитимный трафик - кандидаты на exclusion или снижение уровня параноидальности.
Что показала неделя detection
По webhook-эндпоинту картина была ожидаемо шумная: партнёрские системы присылают XML с nested-структурой, и старые правила на этот счёт были нервными. С CRS 3.2 из этого потока ложноположительными оказались считанные единицы - конкретно два правила, завязанных на XML с длинными строковыми значениями. Оба добавили в exclusion без сожалений.
По личному кабинету - ни одного ложноположительного на весь трафик за неделю. Это неожиданно: с CRS 3.0 на этом же приложении была пара десятков в сутки.
Платёжный шлюз дал несколько срабатываний на запросы с суммами в теле - правило, анализирующее числовые паттерны как потенциальные SQL-операнды. Одно exclusion - и чисто.
Реальных атак за неделю поймали тоже, кстати. Несколько сканирований на SQL injection, один попытка path traversal, и довольно наглая попытка прокинуть shell-команды в User-Agent. Всё это в detection правила поймали корректно.
Переход в blocking
После недели анализа у нас была готова exclusion-конфигурация из пяти строк. Пять строк - не сотни исключений, которые мы накапливали с CRS 3.0 месяцами. Включили SecRuleEngine On, подождали первый час, проверили логи и метрики - никаких инцидентов с легитимным трафиком.
Paranoia level 1 оставили как есть - для этих приложений его достаточно. На уровень 2 планируем переходить осторожно и только там, где риски реально высокие: чувствительный API, работающий с ПДн.
Что стоит иметь в виду
Detection-фаза - не опциональный этап, а обязательный. Одна неделя при нормальном трафике дала нам понимание, которое на CRS 3.0 собиралось месяцами в продакшне под потоком жалоб. При этом трафик должен быть репрезентативным: если недельный срез не покрывает типичные сценарии - брать дольше.
ModSecurity 2.9 с CRS 3.2 работает стабильно, но modsec_audit.log умеет расти очень быстро, особенно в detection-режиме на нагруженном API. Настройки SecAuditLogParts и ротация - не забыть до включения, а не после.
Отдельно стоит пройтись по crs-setup.conf - там много параметров, которые по умолчанию выставлены на общий случай. Для конкретного приложения имеет смысл настроить SecRequestBodyLimit, SecRequestBodyNoFilesLimit и списки разрешённых методов - это снижает нагрузку на движок и сужает поверхность для обхода правил.
Если раньше WAF казался слишком шумным инструментом для продакшна, CRS 3.2 - повод пересмотреть эту позицию. Хотя «пересмотреть» - не значит «поставить и забыть». Правила живут, трафик меняется, exclusion-конфиг нужно периодически ревьюить.