Перейти к содержимому
HoginHogin
Назад

WAF на минималках: защищаем приложение Coraza + OWASP CRS без enterprise-бюджета

8 мин чтения

WAF долго оставался покупкой: облачный сервис с оплатой за трафик или appliance «запросите прайс». Тем временем классический self-hosted вариант — ModSecurity — оброс репутацией хрупкого: C-зависимости, плавающие проблемы nginx-коннектора v3 и неторопливая разработка. В 2026 году появилась рабочая альтернатива без этих trade-off: Coraza — движок WAF на Go, понимающий тот же SecLang и OWASP Core Rule Set v4, — и вписывается он в контейнер размером в десятки мегабайт. Это статья вне цикла про Wazuh, но по соседству с ним: в конце заведём блокировки WAF в тот же SIEM.

Содержание

Открыть содержание

Что WAF реально блокирует — а что нет

WAF — это реверс-прокси с сигнатурным анализом HTTP-запросов: он разбирает путь, заголовки, строку параметров и тело, прогоняет их через набор правил и решает — пропустить или отклонить. OWASP CRS, де-факто стандартный набор таких правил, закрывает типовой арсенал:

Чего WAF не решает — важно проговорить честно, иначе он станет ложным чувством безопасности:

WAF в пути трафика

Отдельно про TLS: в нашей схеме WAF — реверс-прокси, который терминирует шифрование, поэтому правила видят запросы в открытом виде. Следствие простое: не ставьте его после TLS-passthrough балансировщика — там он увидит только шифрованный шум.

WAF — фильтр типовых атак перед приложением, а не замина аудиту, сканированию зависимостей и нормальному access-контролю.

Предыстория: ModSecurity и SecLang

Десятилетиями «self-hosted WAF» означал одно: ModSecurity. Под Apache модуль v2 стабилен, но это легаси-код и отдельный веб-сервер только ради WAF; под nginx коннектор v3 (libmodsecurity) давно известен плавающими segfault’ами и утечками памяти. Развитие проекта замедлилось, а экосистема вокруг — нет: правила OWASP CRS живут своей жизнью и требуют совместимого движка.

Ценное, что ModSecurity оставил после себя, — не код, а SecLang: декларативный язык правил (SecRule, фазы, трансформации, действия), на котором написаны десятки тысяч правил и весь CRS. Поэтому наследники соревнуются не в «своём формате», а в совместимости с ним.

Coraza: движок и обвязка

Coraza — WAF-движок, написанный на Go с нуля, без C-зависимостей. Он понимает SecLang — включая те части v3-синтаксиса, которых давно ждали в ModSecurity — и официально поддерживает OWASP CRS v4. По бенчмаркам ngrok, гонявшего CRS на обоих движках, Coraza на 20–40% быстрее классического ModSecurity под той же нагрузкой. Как библиотека он встраивается куда угодно, а готовые обвязки закрывают основные прокси:

Coraza — движок и обвязка

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

Paranoia levels: охват против false positives

CRS регулирует агрессивность одним числом — paranoia level, от 1 до 4. С каждым уровнем включаются дополнительные группы правил: то, что на PL1 осталось незамеченным, на PL2 матчится и блокируется. Плата предсказуема — рост false positives: легитимные JSON-поля, поиск по товарам, фрагменты кода в тикетах начинают ловиться SQLi- и XSS-правилами.

Шкала paranoia levels

Рабочий цикл тюнинга один и тот же, независимо от уровня:

Уровни 3 и 4 существуют, но в проде редки: они рассчитаны на окружения, где шум — приемлемая цена охвата — фиды для PCI DSS-мониторинга или honeypot’ы, где «легитимного» трафика почти нет. Для обычного приложения разумный потолок — PL2 после вылизанных исключений.

Точечные исключения — та дисциплина, которая отличает работающий WAF от выключенного через месяц из-за «он всё ломает».

ModSecurity против Coraza

ModSecurityCoraza
Язык и зависимостиC, libmodsecurityGo, без C-зависимостей
Язык правилSecLang (родной)SecLang-совместимый
Интеграциимодуль Apache v2, коннектор nginx v3Caddy, Traefik, Envoy (WASM), HAProxy, библиотека
OWASP CRS v4поддерживаетсяподдерживается
Скорость на CRSbaseline+20–40% (бенчмарк ngrok)
Образ контейнерасотни МБдесятки МБ

Что нужно, чтобы завести WAF перед приложением

Нужны Docker и любое приложение, отвечающее на HTTP. Демо-структура:

waf-demo/
├── compose.yaml
└── waf/
    ├── Dockerfile
    ├── Caddyfile
    ├── coraza.conf
    └── crs-tuning.conf

waf/Dockerfile — Caddy, собранный с модулем Coraza, плюс клон CRS:

FROM caddy:2-builder AS builder
RUN xcaddy build --with github.com/corazawaf/coraza-caddy
RUN git clone --depth 1 https://github.com/coreruleset/owasp-modsecurity-crs.git /owasp-crs

FROM caddy:2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
COPY --from=builder /owasp-crs /etc/caddy/owasp-crs
RUN cp /etc/caddy/owasp-crs/crs-setup.conf.example /etc/caddy/owasp-crs/crs-setup.conf
COPY Caddyfile /etc/caddy/Caddyfile
COPY coraza.conf crs-tuning.conf /etc/caddy/

waf/Caddyfile — WAF первой директивой, upstream задаётся одной переменной окружения:

{
	order coraza_waf first
}

waf {
	load_owasp_crs /etc/caddy/owasp-crs
	include /etc/caddy/coraza.conf
}

:8080 {
	coraza_waf waf
	reverse_proxy {$UPSTREAM}
}

waf/coraza.conf — включаем движок, подключаем CRS, включаем JSON-аудит блокировок:

SecRuleEngine On
SecRequestBodyAccess On
SecResponseBodyAccess Off
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyLimitAction Reject

# тюнинг ДО подключения CRS: paranoia level и переменные приложения
Include /etc/caddy/crs-tuning.conf

# OWASP CRS v4: setup + правила
Include /etc/caddy/owasp-crs/crs-setup.conf
Include /etc/caddy/owasp-crs/rules/*.conf

# исключения ПОСЛЕ CRS — действуют на уже загруженные правила
# SecRuleRemoveTargetById 942100 "ARGS:q"

# аудит-лог блокировок: одна JSON-строка на транзакцию
SecAuditEngine RelevantOnly
SecAuditLog /dev/stdout
SecAuditLogFormat JSON

waf/crs-tuning.conf:

# 1 — дефолт, 2 — агрессивнее (поднимайте после тюнинга исключений)
SecAction "id:110000,phase:1,pass,t:none,nolog,setvar:tx.paranoia_level=1"

Нюанс с порядком include: SecRuleRemove* и SecRuleUpdateTarget* действуют на уже загруженные правила, поэтому исключения пишут после Include CRS; а переменные вроде paranoia_level — до, чтобы setup CRS подхватил их при старте.

compose.yaml:

services:
  app:
    image: traefik/whoami
    expose:
      - "80"

  waf:
    build: ./waf
    environment:
      UPSTREAM: app:80
    ports:
      - "8080:8080"
    depends_on:
      - app

Запуск — docker compose up -d --build. Замените traefik/whoami на свой образ, а UPSTREAM — на адрес своего приложения: остальное не меняется.

Как проверить, что всё работает

Чистый запрос проходит, SQLi и XSS — отбиваются на лету:

curl -i http://localhost:8080/                          # 200 — легитимный запрос
curl -i "http://localhost:8080/?id=1%20OR%201%3D1--"     # 403 — SQLi, правило 942100
curl -i "http://localhost:8080/?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E"  # 403 — XSS, 941100

В логе контейнера — JSON-транзакции с ID сработавшего правила:

docker compose logs waf | grep -E '942100|941100'

Каждая транзакция несёт client_ip, URI, код ответа и список сработавших правил с их сообщениями — этого хватает, чтобы разобрать любой блок без угадывания. А ответ 200 на чистом запросе ещё и покажет заголовки whoami: видно, что трафик дошёл до приложения, а не был сгенерирован самим прокси.

Бюджет проверяется той же командой: контейнер укладывается в ~150–200 МБ RAM — WAF вписывается в VPS за $10/мес, на котором живёт само приложение.

Куда девать блокировки

Каждый 403 — это сигнал: кто-то зондирует ваше приложение. Ждать, пока его кто-то заметит в логах, не обязательно — в цикле про Wazuh мы уже строили конвейер «JSON-лог → Fluent Bit → syslog-приёмник менеджера» для аудита Kubernetes. Здесь он повторяется один в один: Fluent Bit хвостит лог контейнера WAF и отправляет строки в syslog на 514/tcp.

[INPUT]
    Name   tail
    Path   /var/log/containers/*waf*.log
    Parser docker

[OUTPUT]
    Name   syslog
    Host   WAZUH_MANAGER_IP
    Port   514
    Format rfc5424

На менеджере JSON-декодер разворачивает транзакцию, а правило цепляется за код ответа — нумерация продолжает серию:

<rule id="100230" level="6">
  <decoded_as>json</decoded_as>
  <field name="transaction.response.http_code">^403$</field>
  <description>waf: request blocked by Coraza — check transaction.messages for rule id</description>
</rule>

Дальше — знакомая механика: уровень, активный ответ на источник при повторах, дашборд. Хосты, аудит кластера и теперь блокировки WAF сходятся в один SIEM.

Итог

WAF «на минималках» — это не enterprise-appliance за пять тысяч долларов в месяц, а один контейнер между пользователем и приложением: Caddy с модулем Coraza, OWASP CRS v4 и час аккуратного тюнининга исключений. Он закрывает 80% типовых веб-атак — SQLi, XSS, traversal, сканеры — за ~150–200 МБ RAM и $0 лицензий, честно не претендуя на баги бизнес-логики и украденные сессии. А блокировки, в свою очередь, — готовые события для SIEM: атака отбита, но осадочек остался — и он у вас в дашборде.


Поделиться:

Следующая статья
Falco в Kubernetes: runtime-защита на уровне syscalls и единый SIEM с Wazuh (часть 6/6)