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

Сегодня мы познакомимся с решением Coraza, развернем его в инфраструктуре Selectel, а также разберем, как писать свои правила для выявления вредоносных запросов. Интересно будет всем, кто так или иначе вовлечен в защиту общедоступных ресурсов. Поехали!

Содержание
Что такое Coraza
Установка и развертывание
Настройка и проверка работы
Написание правил
Тестирование обхода WAF
Выводы

В отличие от классических сетевых экранов, которые фильтруют трафик на сетевом (L3) и транспортном (L4) уровнях модели OSI, WAF работает на самом верхнем — прикладном уровне приложений (L7). Он анализирует HTTP-трафик в режиме реального времени, блокируя SQL-инъекции, межсайтовый скриптинг (XSS), удаленное исполнение кода (RCE) и другие атаки.

Подробнее о различных WAF — и открытых, и проприетарных, в том числе имеющих сертификаты ФСТЭК, —  можно почитать в наших предыдущих статьях.

Появившийся в 2002 году модуль ModSecurity для веб-сервера Apache стал по сути стандартом среди открытых решений класса WAF. В дальнейшем он получил развитие как модуль для Nginx и IIS, заслужив признание мира ИБ. В 2024 году развитие ModSecurity официально завершилось. Сейчас усилиями OWASP выходят лишь обновления для критичных уязвимостей, а сообщество продолжает дописывать правила детектирования.

Однако, идеологически проект не умер — он получил свое продолжение в легковесном OWASP Coraza.

Что такое Coraza

OWASP Coraza — это высокопроизводительный WAF с открытым исходным кодом, полностью написанный на языке Go. Он предназначен для защиты веб-ресурсов от сетевых угроз. Основная задача Coraza — блокировать вредоносные HTTP-запросы на первом эшелоне защиты, еще до того, как те достигнут логики приложения.

Coraza — не форк старого ModSecurity, а полностью переписанный с нуля движок WAF. В отличие от аналогов на C/C++ (как тот же ModSecurity), он решает фундаментальную проблему ИБ: защиту памяти. Уязвимости переполнения буфера в Go исключены на уровне компилятора.

Помимо защиты памяти, к особенностям Coraza можно отнести:

  • совместимость с правилами ModSecurity — «из коробки» поддерживается синтаксис SecLang и  базовый набор OWASP Core Rule Set (CRS) v4;

  • гибкость развертывания — движок может встраиваться как библиотека в Go-приложения, работать в виде плагина для Caddy и Traefik, а также запускаться через WebAssembly (WASM) в Envoy и Istio.

Давайте теперь рассмотрим, процесс обработки HTTP-запросов. Всякий раз, когда очередной запрос попадает в Coraza, он проходит через фиксированные фазы (Phases), на каждой из которых к нему применяются правила (SecLang):

Блок‑схема: последовательность обработки HTTP‑запроса.

Рассмотрим фазы подробнее.

Фаза 1. Заголовки запроса (Request Headers). Обработка начинается сразу после извлечения парсером HTTP‑заголовков, строки URI и файлов cookie. На этом этапе WAF изучает методы и значения заголовков. Еще до передачи тела данных отсекаются сканеры уязвимостей, боты и аномальные запросы с подозрительными методами или заголовками.

Фаза 2. Тело запроса (Request Body). Далее анализируется само тело сообщения — например, JSON, XML или multipart-формы. Правила SecLang проверяют содержимое на наличие инъекций (SQLi, XSS) и вредоносных нагрузок. Для разбора структурированных данных используются внутренние буферы.

Фаза 3. Заголовки ответа (Response Headers). Этот этап наступает, после того, как веб-сервер сформировал ответ и отправил его обратно. WAF анализирует HTTP-статусы и заголовки ответа, предотвращая утечку метаданных сервера или блокируя трафик, если бэкенд ошибочно выдал конфиденциальную информацию.

Фаза 4. Тело ответа (Response Body). На этом шаге проверяется контент, который сервер возвращает клиенту. Правила ищут признаки как утечки данных (номера карт, пароли, системные ошибки бэкенда), так и вредоносного кода, который злоумышленники могли внедрить на скомпрометированный ресурс.

Фаза 5. Логирование (Logging). Финальный этап выполняется асинхронно после отправки ответа клиенту. Собирается контекст транзакции, фиксируются сработавшие правила и дополняется детальный аудит-лог (Audit Log), не задерживая при этом дальнейшую обработку запросов.

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

Высокая скорость обработки трафика обеспечивается за счет нескольких техник:

  • аллокации памяти — движок активно использует пулы объектов sync.Pool и вместо постоянного выделения ресурсов и сборки мусора (GC) для каждого HTTP-запроса, структуры данных переиспользуются, что минимизирует системные задержки;

  • собственных парсеров — внутри реализованы высокооптимизированные модули для разбора MIME, Multipart, JSON и других форматов;

  • стриминга тела запроса — большие файлы не загружаются в RAM целиком, так как помощью директивы SecRequestBodyInMemoryLimit (обычно 128 КБ) данные, превышающие лимит, буферизируются на диск.

Пропускная способность Coraza  измеряется в RPS (Requests Per Second) и зависит от следующих факторов:

  • способ интеграции (архитектура) — прямое встраивание в Go-код или использование плагина для веб-сервера Caddy дает самую высокую скорость;

  • уровень паранойи (Paranoia Level) — это главный фактор нагрузки на процессор (CPU): на базовом уровне (PL1) система проверяет только самые очевидные угрозы, а на максимальном (PL4) запускает сотни тяжелых регулярных выражений для каждого символа, что кратно снижает скорость;

  • размер и тип данных в теле запроса/ответа — проверка коротких HTTP-заголовков происходит мгновенно, но если пользователи загружают файлы, отправляют огромные JSON-пакеты или публикуют длинные тексты, тратится много памяти и процессорного времени на их парсинг и анализ;

  • эффективность регулярных выражений — производительность WAF напрямую связана со сложностью правил SecLang, при этом использование оптимизированного набора правил OWASP Core Rule Set (CRS) версии v4 работает значительно быстрее старых версий благодаря улучшенной логике обработки строк;

  • ограничения ресурсов и сборщик мусора — поскольку Coraza написана на Go, при обработке тяжелого трафика движку приходится часто выделять и освобождать оперативную память, и без грамотной настройки лимитов буфера встроенный сборщик мусора Go может вызывать микропаузы в работе WAF.

В зависимости от настройки тех или иных параметров Coraza может переварить несколько тысяч RPS даже на скромных серверных мощностях.

Перейдем к практической части.

Аренда межсетевого экрана

Защитите свои данные от кибератак и утечек.

Подробнее →

Установка и развертывание

Сначала рассмотрим варианты установки Coraza, а затем разберем развертывание в инфраструктуре на примере Docker-контейнера.

1. Docker Compose

Схема работы Coraza в Docker‑контейнере.

Этот вариант идеально подходит для локальной машины разработчика или тестовых стендов. Здесь используется классическая архитектурная схема Sidecar на уровне единого хоста.

Как это работает

Мы создаем изолированную внутреннюю сеть Docker. Наше целевое веб-приложение помещается в этот контур и настраивается так, чтобы его порт (например, 3000/TCP) не публиковался на хост-машину (в файле docker-compose.yml у приложения отсутствует секция ports, есть только expose). Контейнер с WAF (например, Caddy с плагином Coraza) помещается в эту же сеть, но его порты 80 и 443 остаются доступны снаружи.

Маршрутизация трафика

Внешний HTTP‑запрос попадает на порт WAF. Далее Coraza разбирает данные, прогоняет их по фазам и правилам. Если угрозы не обнаружено, выполняется обратное проксирование, и очищенный трафик перенаправляется на внутреннее имя контейнера приложения — например, http://Web_App:3000/TCP.

2. Docker Swarm

Схема работы Coraza в Docker Swarm.

Это вариант для распределенной среды, когда проект уже вырос из одного сервера, но развертывать полноценный Kubernetes еще избыточно. Здесь защита масштабируется средствами оркестратора Swarm.

Как это работает

Сетевое взаимодействие организуется через общую Overlay-сеть, которая связывает разные физические серверы (ноды) в единый контур. Сервис безопасности разворачивается в режиме mode: replicated и масштабируется на те ноды, где запущены контейнеры на бэкенде.

Маршрутизация трафика

Внешний запрос приходит на балансировщик Swarm Ingress Routing Mesh, который перенаправляет пакет на один из запущенных контейнеров WAF. После успешной проверки внутри Coraza, трафик по внутренней overlay-сети уходит на бэкенд-сервис.

Важный нюанс: по умолчанию встроенный балансировщик Swarm маскирует IP-адреса клиентов, подменяя их внутренними адресами оверлейной сети. Для корректной работы фильтрации это критично: пропадает возможность заблокировать атакующего по IP. Чтобы решить эту проблему, порты для WAF в Docker Swarm необходимо публиковать строго в режиме mode: host. В этом случае контейнер WAF напрямую слушает порт конкретного сервера, минуя Routing Mesh, и видит настоящий IP-адрес клиента.

3. Vanilla Kubernetes

Схема работы Coraza в кластере Kubernetes.

В самостоятельно развернутом кластере Kubernetes, WAF размещается в качестве Sidecar-контейнера внутри одного пода (Pod).

Как это работает

Согласно философии Kubernetes, все контейнеры внутри пода делят общие ресурсы, включая сетевой стек (Network Namespace) и IP-адрес. В нашем случае в один под помещаются оба контейнера: приложение (app) и межсетевой экран (coraza).

Маршрутизация трафика

Контейнер приложения настраивается на работу исключительно с локальным интерфейсом (например, 127.0.0.1:3000) и физически недоступен для других подов или внешнего мира. Контейнер WAF слушает адрес 0.0.0.0:8080 (все интерфейсы пода). Внешний сетевой элемент K8s Service направляет весь входящий трафик пода именно на порт WAF (8080). Coraza принимает запрос, инспектирует его и, если все в порядке, мгновенно пересылает внутри подовской петли (loopback) на 127.0.0.1:3000.

Пример схемы с Docker Compose

Схема работы Coraza в Docker‑контейнере.

Для тестирования на одной виртуальной машине Ubuntu 24.04 LTS лучшим решением будет использование схемы Docker Compose. Мы полностью изолируем уязвимое приложение OWASP Juice Shop во внутренней сети Docker, а в роли WAF развернем веб-сервер Caddy со встроенным плагином OWASP Coraza. Весь внешний трафик будет проходить строго через этот узел.

Шаг 1. Обновление системы и установка Docker

Перед началом работы обновляем пакеты на Ubuntu 24.04 и убеждаемся, что в системе установлены актуальные версии Docker и плагина Docker Compose.

sudo apt update && sudo apt upgrade -y
sudo apt install docker.io docker-compose-v2 -y
sudo usermod -aG docker $USER
newgrp docker

Шаг 2. Создание структуры каталогов проекта

Далее создаем отдельный каталог, в котором будут храниться конфигурационные файлы для WAF и манифест контейнеров:

mkdir -p coraza-compose/coraza-config
cd coraza-compose
touch docker-compose.yml Caddyfile

Шаг 3. Загрузка образов OWASP Juice shop и OWASP Coraza

Скачиваем образ уязвимого веб-приложения:

docker pull bkimminich/juice-shop:latest

Образ Caddy с WAF Coraza:

docker pull openpanel/caddy-coraza:latest

Шаг 4. Скачивание правил

Поскольку выбранный образ поставляется с чистым движком, требуется загрузить правила и плагины, после чего подключить их к контейнеру с WAF.

Скачиваем архив с правилами:

curl \
  -L https://github.com/coreruleset/coreruleset/archive/refs/tags/v4.0.0.tar.gz \
  -o v4.0.0.tar.gz

Извлекаем содержимое:

tar -xzf v4.0.0.tar.gz -C coraza-config/ --strip-components=1

Теперь в директории coraza-config есть все необходимые файлы конфигурации. Сами правила находятся в coraza-config/rules, а пользовательские настройки можно добавлять в файл coraza-config/coraza.conf.

Шаг 5. Настройка Docker Compose

Для корректного совместного запуска контейнеров заполним манифест docker-compose.yml:

networks:
  # Создаем изолированную сеть внутри Docker
  waf-network:
    driver: bridge
services:
  # Уязвимое веб-приложение OWASP Juice Shop
  juice-shop:
    image: bkimminich/juice-shop:latest
    container_name: juice-shop
    expose:
      - "3000"
    networks:
      - waf-network
    restart: always
  # Контейнер с Coraza WAF
  coraza-waf:
    image: openpanel/caddy-coraza:latest
    container_name: coraza-waf
    ports:
      - "80:80"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./coraza-config:/etc/coraza:ro
    networks:
      - waf-network
    depends_on:
      - juice-shop
    restart: always

Как видим, содержимое каталога coraza-config монтируется в /etc/coraza внутри контейнера с Caddy.

Шаг 6. Настройка Caddyfile

Веб-сервер Caddy будет работать как обратный прокси (Reverse Proxy). Он принимает запросы из Интернета, передает их Coraza для проверки и, если все чисто, отправляет в Juice Shop.

Конфигурация Caddy настраивается в файле Caddyfile. Добавим туда директивы для проброса порта и включения Coraza:

{
    # Инициализируем плагин WAF при старте сервера
    order coraza_waf first
}
# Слушаем 80 порт хост-машины
:80 {
    coraza_waf {
        # Все файлы подключаются строго по порядку внутри блока directives
        directives `
            # 1. Загружаем базовую конфигурацию самого движка Coraza
            Include /etc/coraza/coraza.conf            
            # 2. Загружаем настройки порогов атак и паранойи OWASP CRS
            Include /etc/coraza/crs-setup.conf
            # 3. Загружаем плагины правил (обязательное требование для CRS v4)
            Include /etc/coraza/plugins/*-config.conf
            Include /etc/coraza/plugins/*-before.conf
            # 4. Загружаем саму базу защитных правил (SQLi, XSS, LFI и т.д.)
            Include /etc/coraza/rules/*.conf
            # 5. Загружаем финальные штрихи плагинов
            Include /etc/coraza/plugins/*-after.conf
        `
    }
    # Перенаправляем проверенный трафик во внутренний Juice Shop
    reverse_proxy juice-shop:3000
}

Шаг 7. Запуск стенда

Все готово. Поднимаем оба контейнера:

docker compose up -d

Шаг 8. Проверка статуса портов

Убедимся, что порты действительно корректно пробрасываются в контейнеры:

<id контейнера>   openpanel/caddy-coraza:latest   "caddy run --config …"   16 minutes ago   Up 13 minutes   443/tcp, 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 2019/tcp, 443/udp   coraza-waf
<id контейнера>   bkimminich/juice-shop:latest    "/nodejs/bin/node /j…"   16 minutes ago   Up 16 minutes   3000/tcp                                                          juice-shop

Порты прослушиваются. Самое время проверить работоспособность нашей системы. На хосте, откуда обращаемся к сайту, добавим запись в файл hosts с IP‑адресом виртуальной машины и доменом http://test.waf.

Отлично, подготовка стенда завершена. Теперь при переходе по адресу http://test.waf в браузере открывается сайт Juice Shop:

Скриншот странички сайта. Видно шесть карточек и окно предупреждения об использовании cookies.

Coraza имеет три режима работы: 

  • SecRuleEngine On — защита (блокировка), WAF активно проверяет трафик и при определении угрозы отдает статус 403 Forbidden;

  • SecRuleEngine DetectionOnly — мониторинг (тестирование), WAF проверяет трафик и записывает атаки в логи (docker logs), но запросы не блокируются, и клиенты получают статус, который генерирует бэкенд — например, 200 OK, что очень хорошоподходит для проверки правил;

  • SecRuleEngine Off — полное отключение инспекции и пропуск всего трафика без проверок.

Эти значения можно указывать в файле coraza-config/coraza.conf.

Настройка и проверка работы

Как известно, у Juice Shop есть обход авторизации — на странице http://test.waf/#/login можно ввести простейшую SQL-инъекцию, и нас авторизует под учетной записью admin.

Проверим реализацию атаки в каждом из трех режимах, параллельно включив отображение логов контейнера Coraza.

Перед началом тестирования настроим удобный формат логирования. Все события будут записываться в формате JSON и содержать ценные поля — например, нагрузку (payload). В раздел directives файла Caddyfile добавим следующие параметры со значениями:

# 1. Включаем движок аудит-логов
SecAuditEngine On

# 2. Указываем выводить логи прямо в консоль контейнера (stdout)
SecAuditLog /dev/stdout

# 3. Говорим собирать все параметры: и URI (B), и тело запроса (C), и заголовки (F)
SecAuditLogParts ABCFHJKZ
SecAuditLogFormat JSON

Теперь логи станут существенно удобнее для просмотра.

Далее в файле Caddyfile переключаем WAF в режим блокировки:

SecRuleEngine On

Перезапускаем контейнеры:

docker compose down
docker compose up -d

Включаем отображение логов:

docker logs -f <id контейнера>

Отправляем SQL-инъекцию:

curl \
    -X POST http://test.waf/#/login \
    -H "Content-Type: application/json" \
    -d '{"email":"'\'' OR 1=1; -- -","password":"injection"}' \
    -v

Получаем ответ сервера:

HTTP/1.1 200 OK
Accept-Ranges: bytes
Access-Control-Allow-Origin: *
Cache-Control: public, max-age=0
Content-Type: text/html; charset=UTF-8
Date: Sun, 16 Aug 2026 17:26:48 GMT
Etag: W/"24b1-1a00b9bb0b5"
Feature-Policy: payment 'self'
Last-Modified: Sun, 16 Aug 2026 17:25:47 GMT
Vary: Accept-Encoding
Via: 1.1 Caddy
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Recruiting: /#/jobs
Transfer-Encoding: chunked
<!--
  ~ Copyright (c) 2014-2026 Bjoern Kimminich & the OWASP Juice Shop contributors.
  ~ SPDX-License-Identifier: MIT
  -->
<!doctype html>
<html lang="en" data-beasties-container>
<head>
…

Как видим, сервер ответил статусом 200 — SQL-инъекция прошла «на ура». На самом деле, это ожидаемое поведение, потому что инъекция находится в теле POST‑запроса, изучение которого по умолчанию отключено. Чтобы Coraza анализировал тело запроса, необходимо в файл coraza-config/coraza.conf добавить следующую директиву:

SecRequestBodyAccess On

Добавляем этот параметр, перезапускаем контейнеры и снова делаем запрос:

curl \
    -X POST http://test.waf/#/login \
    -H "Content-Type: application/json" \
    -d '{"email":"'\'' OR 1=1; -- -","password":"injection"}' \
    -i

Ответ стал другим:

HTTP/1.1 403 Forbidden
Server: Caddy
Date: Sun, 16 Aug 2026 17:32:35 GMT
Content-Length: 0

Теперь все сработало штатно, инъекция не прошла. 

В логах контейнера видим важные записи, мы их подробно разберем ниже:

{
  "transaction": {
    "timestamp": "2026/08/16 17:39:37",
    "unix_timestamp": 1786901977469201653,
    "id": "rCZwoqjBEDyEWBFN",
    "client_ip": "ip источника",
    "client_port": 0,
    "host_ip": "",
    "host_port": 0,
    "server_id": "test.waf",
    "request": {
      "method": "POST",
      "protocol": "HTTP/1.1",
      "uri": "/",
      "http_version": "",
      "headers": {
        "accept": [
          "*/*"
        ],
        "content-length": [
          "54"
        ],
        "content-type": [
          "application/json"
        ],
        "host": [
          "test.waf"
        ],
        "user-agent": [
          "curl/8.5.0"
        ]
      },
      "body": "{\"email\":\"' OR 1=1; -- -\",\"password\":\"injection_0001\"}",
      "files": null,
      "args": {
      },
      "length": 0
    },
    "response": {
      "protocol": "",
      "status": 0,
      "headers": {
      },
      "body": ""
    },
    "producer": {
      "connector": "",
      "version": "",
      "server": "",
      "rule_engine": "On",
      "stopwatch": "1786901977469201653 1902190; combined=1829773, p1=399243, p2=1381675, p3=0, p4=0, p5=48855",
      "rulesets": [
        "OWASP_CRS/4.0.0"
      ]
    },
    "highest_severity": "",
    "is_interrupted": true
  },
  "messages": [
    {
      "actionset": "OWASP_CRS/4.0.0",
      "message": "SQL Injection Attack Detected via libinjection",
      "error_message": "[client \"ip источника\"] Coraza: Warning. SQL Injection Attack Detected via libinjection [file \"/etc/coraza/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf\"] [line \"8507\"] [id \"942100\"] [rev \"\"] [msg \"SQL Injection Attack Detected via libinjection\"] [data \"Matched Data: s\u00261 found within ARGS_NAMES:{\\\"email\\\":\\\"' OR 1: {\\\"email\\\":\\\"' OR 1\"] [severity \"critical\"] [ver \"OWASP_CRS/4.0.0\"] [maturity \"0\"] [accuracy \"0\"] [tag \"application-multi\"] [tag \"language-multi\"] [tag \"platform-multi\"] [tag \"attack-sqli\"] [tag \"paranoia-level/1\"] [tag \"OWASP_CRS\"] [tag \"capec/1000/152/248/66\"] [tag \"PCI/6.5.2\"] [hostname \"\"] [uri \"/\"] [unique_id \"rCZwoqjBEDyEWBFN\"]",
      "data": {
        "file": "/etc/coraza/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf",
        "line": 8507,
        "id": 942100,
        "rev": "",
        "msg": "SQL Injection Attack Detected via libinjection",
        "data": "Matched Data: s\u00261 found within ARGS_NAMES:{\"email\":\"' OR 1: {\"email\":\"' OR 1",
        "severity": 2,
        "ver": "OWASP_CRS/4.0.0",
        "maturity": 0,
        "accuracy": 0,
        "tags": [
          "application-multi",
          "language-multi",
          "platform-multi",
          "attack-sqli",
          "paranoia-level/1",
          "OWASP_CRS",
          "capec/1000/152/248/66",
          "PCI/6.5.2"
        ],
        "raw": "SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|REQUEST_HEADERS:Referer|ARGS_NAMES|ARGS|XML:/* \"@detectSQLi\" \"id:942100,phase:2,block,capture,t:none,t:utf8toUnicode,t:urlDecodeUni,t:removeNulls,msg:'SQL Injection Attack Detected via libinjection',logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',tag:'application-multi',tag:'language-multi',tag:'platform-multi',tag:'attack-sqli',tag:'paranoia-level/1',tag:'OWASP_CRS',tag:'capec/1000/152/248/66',tag:'PCI/6.5.2',ver:'OWASP_CRS/4.0.0',severity:'CRITICAL',multiMatch,setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}',setvar:'tx.sql_injection_score=+%{tx.critical_anomaly_score}'\""
      }
    },
    {
      "actionset": "OWASP_CRS/4.0.0",
      "message": "Inbound Anomaly Score Exceeded (Total Score: 5)",
      "error_message": "[client \"ip источника\"] Coraza: Access denied (phase 2). Inbound Anomaly Score Exceeded (Total Score: 5) [file \"/etc/coraza/rules/REQUEST-949-BLOCKING-EVALUATION.conf\"] [line \"11202\"] [id \"949110\"] [rev \"\"] [msg \"Inbound Anomaly Score Exceeded (Total Score: 5)\"] [data \"\"] [severity \"emergency\"] [ver \"OWASP_CRS/4.0.0\"] [maturity \"0\"] [accuracy \"0\"] [tag \"anomaly-evaluation\"] [hostname \"\"] [uri \"/\"] [unique_id \"rCZwoqjBEDyEWBFN\"]",
      "data": {
        "file": "/etc/coraza/rules/REQUEST-949-BLOCKING-EVALUATION.conf",
        "line": 11202,
        "id": 949110,
        "rev": "",
        "msg": "Inbound Anomaly Score Exceeded (Total Score: 5)",
        "data": "",
        "severity": 0,
        "ver": "OWASP_CRS/4.0.0",
        "maturity": 0,
        "accuracy": 0,
        "tags": [
          "anomaly-evaluation"
        ],
        "raw": "SecRule TX:BLOCKING_INBOUND_ANOMALY_SCORE \"@ge %{tx.inbound_anomaly_score_threshold}\" \"id:949110,phase:2,deny,t:none,msg:'Inbound Anomaly Score Exceeded (Total Score: %{TX.BLOCKING_INBOUND_ANOMALY_SCORE})',tag:'anomaly-evaluation',ver:'OWASP_CRS/4.0.0'\""
      }
    }
  ]
}

Остановимся подробнее на тех самых важных записях.

В теле запроса отображается нагрузка (сама инъекция):

"body": "{\"email\":\"' OR 1=1; ---\",\"password\":\"injection_0001\"}"

В логах есть сообщение о блокировке:

"message": "SQL Injection Attack Detected via libinjection"

Тег paranoia-level имеет значение 1, что говорит о высокой очевидности атаки:

[tag \"paranoia-level/1\"]

В Coraza параметр paranoia-level варьируется от 1 до 4. На максимальном уровне WAF будет наиболее скрупулезно относиться к параметрам HTTP-запросов. Значение этого параметра можно регулировать в Caddyfile, например:

SecAction "id:900000,phase:1,pass,t:none,nolog,setvar:tx.blocking_paranoia_level=4,setvar:tx.paranoia_level=4"

Разберем, что записано выше:

  • id:900000 — уникальный паспорт правила, номер которого лежит в диапазоне от 900000 до 900999, что в OWASP CRS определено как «системные правила для предварительной настройки WAF»;

  • phase:1 — фаза обработки трафика;

  • pass — действие WAF;

  • t:none — отсутствие трансформаций текста (например, перевод в нижний регистр), так как это системная команда;

  • nolog — режим тишины, который запрещает WAF записывать в журнал факт срабатывания этой конкретной директивы (если этого не сделать, логи забьются миллионами записей о применении настроек для каждого пользователя);

  • setvar:tx.paranoia_level=4 — уровень строгости анализа, который указывает Coraza, какие файлы защитных сигнатур активировать (на четвертом уровне WAF включает все регулярные выражения, что есть в базе OWASP CRS, даже для самой глубокой, тяжелой и параноидальной проверки текста);

  • setvar:tx.blocking_paranoia_level=4 — порог для блокировки, который определяет, на каком уровне строгости WAF должен не просто фиксировать атаки, а именно выдавать статус 403 Forbidden.

Далее указывается файл с сигнатурами, правило из которого было использовано при детектировании атаки:

"file": "/etc/coraza/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf"

И, наконец, информация о прерывании сессии на основе набранного количества баллов, указания фазы анализа, на котором произошло детектирование (о фазах мы говорили в начале статьи):

"message": "Inbound Anomaly Score Exceeded (Total Score: 5)",
"error_message": "[client \"ip источника\"] Coraza: Access denied (phase 2).

В Coraza решение о блокировке принимается на основе значения внутреннего скоринга, который учитывает совокупность вредоносных запросов — их количество и качество.

Пороговое значение скоринга можно регулировать параметром в файле Caddyfile (или в файлах правил):

SecAction "id:900001,phase:1,pass,t:none,nolog,setvar:tx.inbound_anomaly_score_threshold=2"

В строке выше, setvar:tx.inbound_anomaly_score_threshold=2 устанавливает лимит «штрафных» баллов (порог аномалий) для всех входящих запросов. Если какой-то из них наберет хотя бы два балла, Coraza мгновенно прервет соединение и вернет пользователю статус 403 Forbidden.

По умолчанию логи Coraza можно просматривать вручную, но так как они представляют собой обычный JSON, их можно легко отправлять в SIEM-системы, такие как Grafana Loki, ELK и другие. JSON парсится  довольно просто, что позволяет быстро обрабатывать данные и строить разнообразную визуализацию.

В рамках данной статьи отправку логов в сторонние системы рассматривать не станем, чтобы не перегружать материал.

Написание правил

Теперь, когда у нас есть работающий стенд, разберем синтаксис SecLang, унаследованный от ModSecurity. Каждое правило проверяет заданную переменную с помощью некоторого оператора, и, если проверка прошла, выполняет перечисленные действия.

Структура правила:

SecRule ПЕРЕМЕННАЯ "@ОПЕРАТОР АРГУМЕНТ" "id:уникальный_id,фаза,действие1,действие2,msg:'Текст ошибки'"

Переменные задают элемент для поиска в структуре запроса, например:

  • REQUEST_URI — полный URI запроса, например, /index.php?id=1;

  • REQUEST_HEADERS:<имя> — конкретный заголовок, например, REQUEST_HEADERS:User-Agent;

  • ARGS_GET или ARGS_POST — GET‑ и POST‑параметры запроса.

Операторы определяют способ поиска и всегда начинаются с «@»::

  • @rx — регулярное выражение (самый частый случай);

  • @pm (Pattern Match) — сверка по списку слов работает значительно быстрее регулярных выражений;

  • @detectXSS и @detectSQLi — вызов высокоскоростных встроенных плагинов анализа на базе библиотеки libinjection.

Действия могут включать в себя трансформации для подготовки данных перед проверкой:

  • t:lowercase — привести к нижнему регистру;

  • t:urlDecode — декодировать URL-сущности, например, превратить «%20» в пробел.

Подготовим несколько примеров правил.

Первое будет для блокировки сканеров уязвимостей по значению заголовка User-Agent:

SecRule REQUEST_HEADERS:User-Agent \
"@pm sqlmap nmap nikto acunetix" \
    "id:100001,\
    phase:1,\
    deny,\
    status:403,\
    t:lowercase,\
    msg:'Malicious scan tool detected in User-Agent'"

Что делает правило выше:

  • REQUEST_HEADERS:User-Agent — извлекает значение заголовка User-Agent;

  • t:lowercase — переводит в нижний регистр;

  • @pm sqlmap nmap… — ищет совпадения по маске;

  • status:403 — если находит, сбрасывает запрос с кодом 403.

Второе правило настроим на поиск специфичной строки «HELLO HABR :)»:

SecRule REQUEST_BODY "@contains HELLO HABR :)" \
    "id:100002,\
    phase:2,\
    deny,\
    status:500,\
    msg:'HELLO :)',\
    log”

Добавлять правила  можно и в Caddyfile, и создавать для них отдельные файлы в каталоге coraza-config/rules — при старте Caddy они подтянутся автоматически.

Тестирование обхода WAF

И по традиции в завершение обзора запустим утилиту waf-bypass для проверки обхода Coraza. Ее установка и запуск не отличается от способов, описанных в предыдущих статьях цикла.

Запускаем:

python3 /opt/waf-bypass/main.py --host=http://test.waf

И получаем результат:

Скриншот табличного вывода в терминал.

Как видим, «реализовать» получилось 37,27% атак, что для настроек «из коробки» — довольно неплохой результат. В реальных условиях процент успешных обходов существенно снижается за счет повышения уровня паранойи и точечной доработки правил детектирования.

Выводы

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

Стоит отметить, что настройки WAF по умолчанию требуют обязательной доработки под конкретную систему (что наглядно подтвердилось на примере отключенной обработки POST-запросов). Однако механизм скоринга и регулируемые уровни паранойи позволяют адаптировать строгость фильтрации максимально гибко.

Если дополнить встроенную систему логирования инструментами визуализации, получится удобный и эффективный комплекс защиты веб-приложений.

Комментарии (0)