ModSecurity с набором правил OWASP CRS включают за десять минут, а выключают через три дня — после того, как перестают сохраняться статьи в админке, ломается загрузка файлов и клиент не может оформить заказ, потому что в адресе оказалась кавычка. Вывод «WAF мешает работать» напрашивается сам, но он неверный: почти все эти блокировки лечатся тремя-четырьмя точечными исключениями, и всё дело в том, чтобы найти их правильно.
Не включайте блокировку сразу
Первая неделя — только наблюдение. В /etc/modsecurity/modsecurity.conf:
SecRuleEngine DetectionOnly
В этом режиме WAF пишет всё, что заблокировал бы, но ничего не блокирует. Неделя реального трафика — включая ваши собственные действия в админке, загрузку картинок и оформление заказа — даст список настоящих ложных срабатываний, а не гипотетических. Переключать на On имеет смысл только после того, как этот список разобран.
Как устроен CRS: не одно правило, а сумма
Ключ к пониманию всего дальнейшего. CRS почти никогда не блокирует запрос одним правилом. Каждое сработавшее правило добавляет запросу баллы аномальности, и блокировка происходит, когда сумма превысит порог. Поэтому в логе вы увидите не одну строку, а несколько, и последней будет правило с идентификатором 949110 — то самое, которое подводит итог.
Практический вывод: исключение надо делать для правила, которое набрало баллы, а не для 949110. Отключив итоговое правило, вы отключите весь набор целиком, и WAF останется только в виде строчки в конфиге.
Второе следствие — уровень паранойи (paranoia level). По умолчанию он первый, и это правильный выбор. Уровни 2 и 3 добавляют правила, заведомо дающие ложные срабатывания на обычных сайтах, и включать их стоит, только когда первый уровень отлажен полностью.
Найти виноватое правило
Всё нужное есть в audit log (/var/log/modsec_audit.log) и в логе ошибок веб-сервера. Ищем по времени блокировки:
sudo grep -o 'id "[0-9]*"' /var/log/modsec_audit.log | sort | uniq -c | sort -rn | head
Это частотный список сработавших правил. Дальше по конкретному идентификатору смотрим, на чём именно оно сработало:
sudo grep -A5 'id "942100"' /var/log/modsec_audit.log | head -40
Нужны три вещи: идентификатор правила, имя параметра (ARGS:content, ARGS:comment) и путь запроса. Из них и строится исключение.
Обычные подозреваемые
Список повторяется от сайта к сайту:
- 942100 — SQL-инъекция. Срабатывает на текстовых полях с длинным содержимым: тело статьи, описание товара, комментарий. Кавычки, скобки и слова вроде
selectв нормальном тексте выглядят для детектора подозрительно; - 941100 и 941xxx — XSS. Приходят вместе с визуальным редактором: HTML-теги в поле и есть его смысл работы;
- 920420 — неразрешённый Content-Type. Ломает API и загрузку файлов: набор разрешённых типов по умолчанию узкий и
application/jsonв старых версиях в него не входил; - 913100 — сканер по User-Agent. Ловит вместе со сканерами и легальные инструменты: мониторинг доступности, curl в ваших же скриптах;
- 200002, 200004 — ошибки разбора тела запроса. Обычно означают не атаку, а превышенный лимит на размер тела, то есть загрузку крупного файла.
Три способа сделать исключение
По возрастанию грубости. Все они пишутся в свой файл (например, /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES.conf), а не в файлы самого CRS: набор правил обновляется, и ваши правки при обновлении исчезнут.
Убрать один параметр из-под одного правила. Самый точный вариант, к нему и стремимся:
SecRuleUpdateTargetById 942100 "!ARGS:content"
Отключить правило только на одном пути. Годится, когда «шумит» конкретная страница — редактор, импорт, форма отзыва:
SecRule REQUEST_URI "@beginsWith /admin/post" \
"id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=942100"
Отключить правило целиком. Крайняя мера и почти всегда признак того, что причину не нашли:
SecRuleRemoveById 942100
Разница между первым и третьим — существенная. В первом случае поле «текст статьи» перестаёт проверяться на SQL-инъекции у одного правила; во втором и третьем перестаёт проверяться весь сайт. Разница в объёме работы — минут пять.
После правок — проверка конфигурации и мягкий перезапуск:
sudo apachectl configtest && sudo systemctl reload apache2
sudo nginx -t && sudo systemctl reload nginx
Порядок, который экономит неделю
- Неделя в
DetectionOnlyс обычной работой на сайте, включая админку и загрузки. - Частотный список правил из audit log. Разбирать сверху вниз — первые три-четыре дадут девяносто процентов шума.
- По каждому: понять, какой параметр и на какой странице. Исключение делать по параметру, а не по правилу.
- Только теперь
SecRuleEngine On. - Раз в месяц заглядывать в блокировки: изменился сайт — появились новые ложные срабатывания.
Последний пункт и есть то, обо что всё обычно разбивается: audit log — это гигабайты текста, читать его вручную никто не станет. Смысл собранной панели в том, чтобы список сработавших правил, заблокированных запросов и активного набора был перед глазами, а не добывался из grep. Как это выглядит — на странице демо ниже.