MAATRIX / Блог / Миф: WAF закрывает дыры в коде

Миф: WAF закрывает дыры в коде

MAATRIX

«Поставили WAF — можно не торопиться с патчем» — фраза, которую слышишь на разборе инцидента почти в каждом втором проекте. WAF действительно снижает риск, но он не трогает код, в котором сидит уязвимость: он фильтрует запросы снаружи, а дыра как была, так и остаётся внутри. Разберём, что WAF умеет на самом деле, где он ломается и как встроить его в защиту так, чтобы не подставиться в момент, когда фильтр не сработает.

Что WAF действительно делает

WAF (Web Application Firewall) — это прослойка между клиентом и приложением, которая разбирает HTTP-запрос и сравнивает его с набором правил: сигнатур известных атак, регулярных выражений, эвристик по частоте запросов. Он смотрит на заголовки, тело запроса, параметры URL, куки — и решает, пропустить запрос дальше, залогировать или заблокировать.

Ключевое ограничение вытекает прямо из этого описания: WAF работает на уровне HTTP-трафика, а не на уровне бизнес-логики приложения. Он не знает, что происходит с данными после того, как запрос прошёл фильтр — как они попадают в SQL-запрос, в шаблон рендеринга, в вызов внешнего API. Он видит форму запроса, но не его последствия внутри вашего кода.

Это не недостаток конкретной реализации — это принцип работы любого WAF, от простого модуля правил на nginx до дорогого облачного сервиса с машинным обучением. Инструмент фильтрует по паттернам на входе, а не анализирует, что код сделает с данными дальше.

Сигнатура — это не понимание логики

Классический пример: правило WAF ищет в параметрах запроса подстроки вида ' OR '1'='1 или UNION SELECT. Если приложение строит SQL-запрос конкатенацией строк без параметризации, такое правило действительно отсекает самые грубые попытки инъекции. Но сама уязвимость никуда не делась — код по-прежнему собирает запрос небезопасно.

Проблема в том, что WAF-правила пишутся под известные, общие паттерны атак — то, что подходит для типичных фреймворков и типичных SQL-диалектов. Ваше приложение может принимать вход не только через строковый параметр в URL, но и через:

  • JSON-поле, вложенное на третий уровень, которое обработчик правил не разбирает так же тщательно, как query-параметры;
  • заголовок X-Forwarded-For или кастомный заголовок, который потом используется в запросе к базе или в логировании через eval-подобный механизм;
  • multipart-форму с именем поля, которое не попадает под типовой шаблон;
  • GraphQL-запрос, где один и тот же эндпоинт принимает десятки разных операций с разной структурой полей.

Ни один общий набор правил не покрывает специфику именно вашего кода. А есть и целый класс проблем, которые WAF в принципе не видит, потому что на уровне HTTP-запроса они выглядят абсолютно легитимно: небезопасный прямой доступ к объекту по ID (IDOR), обход авторизации между ролями, гонки при списании средств, манипуляция ценой товара через изменение поля, которое сервер не должен был доверять клиенту. Структурно это обычные запросы с обычными параметрами — блокировать там нечего, потому что синтаксически всё корректно. Ошибка — в логике обработчика, а не в форме запроса.

Если разбираться в векторах на уровне сервера, полезно посмотреть на статью о защите от SQL-инъекций на уровне сервера — там как раз про то, что реально закрывает проблему (параметризованные запросы, права БД), а что только снижает шанс случайной эксплуатации.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Как WAF обходят на практике

WAF — это в первую очередь набор регулярных выражений и эвристик, а у любой регулярки есть слепые зоны. Атакующий, который целенаправленно работает против конкретного приложения, использует это так:

Обфускация payload. Простое правило ловит ' OR '1'='1, но не обязательно ловит вариант с изменённым регистром (' oR '1'='1), вставкой SQL-комментариев между токенами ('//OR//'1'='1), двойным URL-кодированием (%2527%2520OR%2520%25271%2527%253D%25271) или использованием эквивалентных Unicode-символов, которые нормализуются в опасный символ уже после прохождения фильтра, но до попадания в парсер приложения. Каждый такой трюк по отдельности несложно закрыть отдельным правилом — но набор обходов растёт быстрее, чем набор правил, если основная проблема (небезопасная сборка запроса) не устранена.

Векторы, специфичные для приложения. Готовые наборы правил заточены под типовые уязвимости популярных CMS и фреймворков. Если у вас самописный бэкенд с нестандартной сериализацией данных, кастомным протоколом между фронтом и API или собственной логикой десериализации — типовые сигнатуры просто не рассчитаны на такие форматы. Атакующий, изучивший конкретно ваше приложение (через открытый исходный код, утечку staging-окружения или банальный перебор), находит путь, для которого готового правила ещё не написали.

HTTP Parameter Pollution и разница в парсинге. WAF и бэкенд-приложение иногда по-разному интерпретируют повторяющиеся параметры (id=1&id=2), разные типы кодирования тела запроса или chunked-передачу. Если WAF анализирует запрос иначе, чем его потом разбирает сервер приложений, можно составить запрос, который выглядит безопасным для фильтра и опасным для парсера бэкенда.

Ни один из этих обходов не требует уязвимости в самом WAF — они эксплуатируют фундаментальный разрыв между «похоже на известную атаку» и «действительно опасно для конкретного кода».

Ложные срабатывания и их цена

Обратная сторона строгих правил — блокировка легитимного трафика. Чем агрессивнее настроен WAF, тем больше он ловит случайных совпадений: пользователь пишет в форме обратной связи текст, где случайно встречается последовательность символов, похожая на паттерн атаки; API-клиент отправляет JSON с полем, содержащим символы <, >, ', которые в других контекстах — признак XSS-инъекции; загрузка файла с определённым MIME-типом блокируется, потому что похожа на попытку path traversal.

Результат — реальные пользователи получают 403, легитимные вебхуки от внешних сервисов молча отбрасываются (и вы узнаёте об этом через неделю, когда партнёр спросит, почему у него не проходят уведомления), а служба поддержки заваливается тикетами «не могу отправить форму». На проде это часто хуже, чем сама атака, которую вы пытались предотвратить, — атака происходит редко, а ложные срабатывания при плохой настройке случаются каждый день.

Практический вывод: любое новое правило WAF стоит сначала включать в режиме логирования без блокировки (detection-only), смотреть статистику за несколько дней-недель, и только после этого переводить в режим блокировки. Пример конфигурации на уровне nginx для похожего подхода — сначала логировать подозрительный паттерн, не обрывая соединение:

# Иллюстративный пример: помечаем подозрительный запрос в лог,
# но не блокируем — сначала собираем статистику ложных срабатываний
map $request_uri $suspicious_pattern {
    default 0;
    "~*(\bUNION\b.*\bSELECT\b)"  1;
    "~*(\.\./){2,}"              1;
}

log_format waf_detect '$remote_addr - $time_local "$request" '
                       'suspicious=$suspicious_pattern status=$status';

access_log /var/log/nginx/waf_detect.log waf_detect if=$suspicious_pattern;

Это упрощённая иллюстрация принципа, а не готовый WAF — на проде для полноценной фильтрации нужен специализированный модуль или сервис с нормализацией кодировок, инспекцией тела запроса и управляемым набором правил. Но сама логика — «сначала наблюдать, потом блокировать» — верна для любого инструмента такого рода.

Место WAF в стратегии защиты

Правильная модель — defense in depth, эшелонированная защита, где WAF занимает одну из внешних позиций, а не единственную. Слои примерно такие: сетевая фильтрация и лимиты на уровне инфраструктуры → WAF/reverse proxy с правилами → само приложение с безопасным кодом (параметризованные запросы, валидация и санитизация входа, экранирование вывода) → права доступа на уровне базы данных по принципу наименьших привилегий → мониторинг и логирование → бэкапы на случай, если всё остальное не сработало.

Уберите WAF из этой цепочки — при аккуратно написанном коде и правильных правах в базе прямая SQL-инъекция или XSS всё равно не пройдут, просто у вас будет на один слой меньше телеметрии и защиты от массовых автоматизированных сканеров. Уберите вместо этого безопасный код и права доступа, оставив только WAF, — и вы полагаетесь на то, что фильтр правил угадает все возможные обходы для конкретно вашего приложения. Это не симметрично: WAF без исправленного кода — тонкий слой, код без WAF — рабочая защита с меньшим запасом прочности на массовые атаки.

Единственный сценарий, где WAF оправданно становится временным решением вместо немедленного патча, — virtual patching. Если пентест, автоматический сканер зависимостей или отчёт по программе bug bounty вскрыл конкретную уязвимость, а исправление кода требует тестирования и релизного цикла (иногда дни, если меняется схема данных или контракт API), точечное правило WAF, блокирующее именно этот вектор эксплуатации, — разумная временная мера. Она покупает время, а не закрывает проблему. У такой меры обязательно должен быть срок жизни и тикет с фактическим фиксом кода — иначе временное правило превращается в постоянную заплатку, которая маскирует технический долг на годы вперёд.

Как использовать WAF правильно на практике

Порядок действий, который снижает риск, а не создаёт иллюзию защищённости:

  1. Найдите реальные уязвимости, а не полагайтесь на то, что WAF их «прикроет». Статический анализ кода (SAST), динамическое сканирование приложения (DAST), ручной код-ревью критичных мест — работы с БД, аутентификации, загрузки файлов.
  2. Приоритизируйте настоящий фикс: параметризованные запросы вместо конкатенации строк, валидация входа по allow-list, а не по blacklist, экранирование вывода на стороне шаблонизатора, минимальные права у пользователя БД (веб-приложению обычно не нужен DROP TABLE).
  3. Используйте WAF-правило как временную заглушку только когда фикс кода уже поставлен в план и у него есть срок. Свяжите правило с конкретным тикетом, чтобы через полгода никто не спрашивал, зачем оно там.
  4. Тестируйте новые правила в режиме логирования, прежде чем включать блокировку — это единственный способ не положить легитимный трафик в проде.
  5. Мониторьте оба направления: и то, что WAF заблокировал (возможные реальные атаки или ложные срабатывания), и то, что прошло без блокировки, но выглядит подозрительно в логах приложения.
  6. Не доверяйте WAF бизнес-логику: авторизацию, разграничение доступа, проверки владения ресурсом реализуйте в коде приложения — фильтр на границе сети структурно не может их проверить.

Если ещё не выстроен базовый порядок на сервере — прежде чем добавлять WAF как ещё один слой, стоит закрыть основы: обновления, права доступа, минимальный набор открытых портов. Отправная точка — чек-лист безопасности нового сервера. А если интересно, как быстро уязвимость превращается в реальный инцидент при затянутом патче, разбор конкретного случая — в статье про то, как обновление плагина CMS открыло вход и что можно было сделать раньше.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Если поставить WAF, можно ли откладывать патч уязвимости на потом?

Можно ненадолго и только если у отсрочки есть срок и конкретное правило, закрывающее именно этот вектор. Без плана на фикс отсрочка превращается в постоянное состояние, а WAF рано или поздно обходят — вопрос времени и мотивации атакующего.

WAF защищает от всех типов атак одинаково хорошо?

Нет. Против массовых автоматизированных сканеров и типовых инъекций по известным сигнатурам он эффективен. Против целевой атаки на конкретную бизнес-логику вашего приложения (IDOR, обход авторизации, манипуляция ценой) — почти бесполезен, потому что такие запросы структурно не отличаются от легитимных.

Нужен ли WAF, если код написан безопасно с самого начала?

Он всё равно добавляет пользу как ещё один слой — снижает шум от automated-сканеров, даёт дополнительную точку логирования и время на реакцию при 0-day в используемых библиотеках. Но это дополнение к безопасному коду, а не его замена.

Как понять, что WAF настроен слишком строго?

Растёт число обращений в поддержку про «не отправляется форма», падает конверсия на конкретных шагах без видимой причины, во внешних интеграциях (вебхуки, API-партнёры) внезапно пропадают запросы. Первый шаг диагностики — смотреть лог блокировок WAF за тот же период.

Чем WAF отличается от fail2ban или похожих инструментов защиты по IP?

Это разные уровни: fail2ban реагирует на паттерны в логах (например, серию неудачных попыток входа) и банит IP на уровне сети, а WAF разбирает содержимое каждого HTTP-запроса. У обоих одна и та же ловушка — ощущение полной защиты при отсутствии исправленных уязвимостей в коде; подробнее это разобрано в статье про миф о том, что fail2ban защищает сервер от взлома.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →