Безопасность сервера 14 мин чтения 2026-10-17

Защита веб-приложений от атак с Coraza WAF: развертывание современного аналога ModSecurity и OWASP CRS в Docker Compose

Развертывание высокопроизводительного Web Application Firewall на базе OWASP Coraza и Caddy в Docker Compose: фильтрация SQLi, XSS, Path Traversal и настройка правил OWASP CRS v4 без сбоев.

Инженерная редакция SysKit Проверено инженерами SysKit.ru

В типовой конфигурации веб-сервера безопасность сайта ограничивается связкой фаервола UFW на сетевом уровне и утилиты Fail2ban, сканирующей системные логи. Однако при современных веб-атаках злоумышленники не перегружают сервер миллионами пакетов и не подбирают пароль к SSH. Они отправляют одиночный HTTP POST-запрос с SQL-инъекцией в форму поиска, внедряют XSS-скрипт через комментарии или вызывают уязвимость удаленного исполнения кода (RCE) через заголовок User-Agent.

Для фаервола L3/L4 такой запрос абсолютно легитимен: он пришел на открытый порт 443 по зашифрованному протоколу HTTPS. Fail2ban также бессилен — он анализирует логи постфактум, когда база данных уже скомпрометирована или на сервер залит веб-шелл. Единственным барьером для глубокого анализа полезной нагрузки является Web Application Firewall (WAF) седьмого прикладного уровня модели OSI.

Долгое время стандартом де-факто оставался ModSecurity v3. Однако его тяжелая кодовая база на C++, известные сложности с фрагментацией памяти при обработке многопоточных потоков и официальное объявление Trustwave о прекращении поддержки (EOL с июля 2024 года с передачей кода сообществу) превратили интеграцию ModSecurity в бесконечную борьбу с нестабильностью при обновлениях Nginx. Современной альтернативой стал OWASP Coraza WAF — высокопроизводительный движок на языке Go с безопасностью памяти, совместимостью с синтаксисом SecLang и поддержкой официального набора правил OWASP Core Rule Set (CRS v4). В официальной конфигурации @coraza.conf-recommended инспекция тела ответов бэкенда (SecResponseBodyAccess) отключена по умолчанию для экономии памяти, а ряд тяжелых операторов потоковой постобработки ответов ModSecurity в Coraza опущены как избыточные. Для инспекции входящего трафика веб-сайтов и REST API (заголовки, URI, тела POST) движок полностью закрывает потребности промышленной безопасности.

1. Почему Fail2ban и базового фаервола недостаточно: анатомия L7-атак

Сетевой фаервол операционной системы (iptables, nftables, UFW) работает на сетевом (L3) и транспортном (L4) уровнях. Его задача — фильтровать трафик по IP-адресам, портам и протоколам (TCP/UDP). Он физически не способен декодировать TLS-трафик и анализировать содержимое HTTP-заголовков, параметры URL или JSON-тело POST-запроса.

Fail2ban работает на основе регулярных выражений по текстовым файлам журналов. Он отслеживает частоту появления статус-кодов (например, 401 Unauthorized или 404 Not Found). Если же атакующий использует SQL-инъекцию (SQLi) вида ' UNION SELECT null, username, password FROM users--, веб-сервер отвечает кодом HTTP 200 OK. В логах нет ошибок, и Fail2ban не предпринимает никаких действий.

Уровень защиты Инструменты Что анализирует От чего защищает
Сетевой (L3/L4) UFW, iptables, nftables IP-адреса, TCP/UDP порты, SYN-флуд Сетевой DDoS, закрытие лишних портов
Поведенческий (Логи) Fail2ban, CrowdSec Статус-коды 4xx/5xx в access.log Брутфорс SSH/паролей, сканирование 404
Прикладной WAF (L7) OWASP Coraza, ModSecurity Payload, заголовки, куки, URI, тела POST SQLi, XSS, RCE, Path Traversal, SSRF

Coraza WAF выполняет инспекцию потока данных в реальном времени. Если во входящем запросе обнаруживается сигнатура атаки или аномальный вес скоринга уязвимости, WAF мгновенно разрывает соединение или возвращает статус 403 Forbidden еще до того, как запрос дойдет до PHP-FPM, Node.js, Python или базы данных.

⚠️ Важное предупреждение

Никогда не включайте блокирующий режим WAF вслепую на работающем боевом сайте. Сложные современные CMS (WordPress, Bitrix, Laravel) при сохранении контента из визуальных редакторов передают в POST-запросах HTML-теги и SQL-подобные фрагменты. Без предварительного периода аудита в режиме DetectionOnly вы гарантированно заблокируете легитимных администраторов и пользователей.

2. Архитектура решения: связка Coraza WAF и Caddy Reverse Proxy

Для интеграции Coraza WAF на VDS наиболее практичным подходом является развертывание в качестве фронтального обратного прокси-сервера (Reverse Proxy). Хотя коннектор libcoraza для Nginx активно развивается сообществом, он пока имеет статус экспериментального и требует динамической компиляции под точную версию ABI веб-сервера (что создает риск несовместимости при системных обновлениях). В свою очередь, связка с Caddy 2 через официальный модуль coraza-caddy является наиболее проверенным на практике решением (в репозитории проекта плагин имеет официальный статус stable, needs a maintainer):

  • Автоматический выпуск и продление SSL: Caddy берет на себя получение бесплатных сертификатов Let's Encrypt и ZeroSSL с поддержкой современных протоколов TLS 1.3 и HTTP/3 (QUIC). Проверить состояние шифрования всегда можно через онлайн-чекер SSL-сертификатов.
  • Встроенный набор правил (Embedded CRS): Директива load_owasp_crs использует встроенный в Go-бинарник набор правил OWASP CRS v4. Администратору не нужно вручную скачивать репозиторий правил через Git, следить за путями и монтировать сторонние директории в контейнер.
  • Сетевая изоляция бэкенда: Все защищаемые приложения (веб-сайты, API-сервисы, базы данных) остаются в закрытой внутренней сети Docker. Открытыми наружу остаются только порты 80 и 443 прокси-сервера WAF. Проверить отсутствие утечек портов в публичный интернет поможет сканер открытых сетевых портов.
  • Сквозная маршрутизация: Caddy поддерживает удобную конфигурацию обратного проксирования через файл Caddyfile, который на порядок компактнее и понятнее тяжелых конфигов Nginx. Для быстрого развертывания типовых сценариев используйте генератор конфигураций Caddy Reverse Proxy.

3. Сборка контейнера: Multi-stage Dockerfile с плагином Coraza

Для добавления плагина Coraza в Caddy используется официальный механизм сборки xcaddy. Наиболее чистый и воспроизводимый подход для боевых серверов — двухэтапная сборка (Multi-stage build) в файле Dockerfile. На первом этапе компилируется исполняемый файл, а на втором формируется компактный образ на базе Alpine Linux без лишних компиляторов и сборочных зависимостей.

Создайте рабочую директорию на сервере и перейдите в нее:

mkdir -p /opt/coraza-waf/logs && cd /opt/coraza-waf
# Убеждаемся, что директория логов на хосте доступна для записи процессу контейнера
chmod 775 logs

Создайте файл Dockerfile:

# Этап 1: Компиляция Caddy с модулем Coraza WAF через xcaddy
FROM caddy:2-builder AS builder

RUN xcaddy build \
    --with github.com/corazawaf/coraza-caddy/v2

# Этап 2: Финальный легковесный образ для продакшена
FROM caddy:2-alpine

# Копируем скомпилированный бинарник
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

# Создаем директорию под журналы аудита WAF
RUN mkdir -p /var/log/coraza && chown -R caddy:caddy /var/log/coraza

CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]

💡 Совет инженера

Модуль github.com/corazawaf/coraza-caddy/v2 уже содержит внутри скомпилированный репозиторий правил OWASP Core Rule Set v4. Вам не требуется скачивать внешние репозитории через git clone или отслеживать совместимость версий SecLang — всё готово к работе из коробки.

4. Манифест Docker Compose с лимитами и сетевой изоляцией

В спецификации Compose Specification каноническим способом распределения ресурсов является блок deploy.resources.limits. Для максимальной совместимости (как в автономном Docker Compose, так и при оркестрации) его комбинируют с директивами mem_limit и cpus, которые транслируются напрямую в параметры cgroups ядра Linux:

services:
  waf:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: coraza_waf_proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp" # Поддержка HTTP/3 QUIC
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./logs:/var/log/coraza
      - caddy_data:/data
      - caddy_config:/config
    deploy:
      resources:
        limits:
          cpus: '1.5'
          memory: 1024M
    mem_limit: 1024m
    cpus: 1.5
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:80/"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    networks:
      - waf_public_net
      - waf_internal_net
    depends_on:
      - backend_app

  backend_app:
    image: traefik/whoami:v1.10.1
    container_name: demo_backend
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
    mem_limit: 512m
    cpus: 1.0
    networks:
      - waf_internal_net
    # Порты наружу не публикуем: доступ только через WAF

volumes:
  caddy_data:
  caddy_config:

networks:
  waf_public_net:
    driver: bridge
  waf_internal_net:
    driver: bridge
    internal: true # Изолированная сеть без прямого выхода в интернет

5. Настройка Caddyfile: фазы SecRuleEngine и OWASP CRS v4

Файл конфигурации Caddyfile связывает возможности обратного прокси и директивы фаервола SecLang. Для активации инспекции пакетов перед отправкой на бэкенд необходимо обязательно задать директиву порядка выполнения: order coraza_waf first.

Создайте файл Caddyfile (замените example.com на ваш рабочий домен или используйте порт :80 для локальных тестов):

{
    # Обязательная директива порядка: WAF должен фильтровать запросы ПЕРВЫМ
    order coraza_waf first
}

example.com {
    encode gzip zstd

    coraza_waf {
        # Активация встроенного набора правил OWASP CRS v4
        load_owasp_crs

        directives `
            Include @coraza.conf-recommended
            Include @crs-setup.conf.example
            Include @owasp_crs/*.conf

            # ФАЗА 1: Режим инспекции (DetectionOnly для безопасного аудита)
            SecRuleEngine DetectionOnly

            # Настройка журнала аудита инцидентов
            SecAuditEngine RelevantOnly
            SecAuditLog /var/log/coraza/audit.log
            SecAuditLogParts ABIJDEFHZ
            SecAuditLogType Serial
            SecAuditLogFormat JSON

            # Лимиты на размер тела запроса (защита от переполнения буфера)
            SecRequestBodyAccess On
            SecRequestBodyLimit 10485760
            SecRequestBodyNoFilesLimit 1048576
        `
    }

    # Проксирование проверенного трафика на изолированный бэкенд
    reverse_proxy backend_app:80
}

Обратите внимание на директиву SecRuleEngine DetectionOnly. В этом режиме WAF выполняет полную инспекцию всего входящего трафика, рассчитывает скоринг аномалий и записывает срабатывания в журнал, но не блокирует запросы. Это позволяет проанализировать поведение реальных пользователей в течение 24–48 часов, выявить ложные срабатывания и только затем перейти в боевой режим SecRuleEngine On.

Для управления процессами и горячей перезагрузки правил Caddy использует встроенный Admin API на локальном порту 127.0.0.1:2019. Этот сокет активен внутри сетевого пространства контейнера по умолчанию и не требует открытия наружу, обеспечивая безопасное выполнение команд через docker compose exec.

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

Рекомендуемые VDS-провайдеры

Отказоустойчивые сервера с быстрыми NVMe и каналом до 1 Гбит/с

Выбор редакции
⚡

Timeweb Cloud — Быстрые NVMe VDS

Высокочастотные ядра 3.3+ ГГц для мгновенной фильтрации L7-трафика на лету, NVMe-диски и быстрый канал до 1 Гбит/с.

Конфигурация
1 vCPU 3.3 ГГц • 1 ГБ RAM • 15 ГБ NVMe • Трафик без лимита
Премиум Tier III
🔷

Selectel — Надежные облачные серверы

Изолированная инфраструктура в защищенных дата-центрах Tier III, выделенные vCPU и гибкое управление приватными подсетями.

Конфигурация
2 vCPU • 2 ГБ RAM • 30 ГБ NVMe • Дата-центры Tier III
Простота и стабильность
🟠

Beget — Простота для веб-мастеров

Мгновенный деплой Docker-образов в 1 клик, наглядный мониторинг системных ресурсов, автоматические ежедневные бэкапы и отзывчивый саппорт 24/7.

Конфигурация
1 vCPU • 1 ГБ RAM • 15 ГБ NVMe • Бесплатные бэкапы

6. Обработка ложных срабатываний (False Positives) и исключения для CMS

Набор правил OWASP Core Rule Set v4 использует многоуровневую систему оценки подозрительности (Anomaly Scoring) и разделен на 4 уровня паранойи (Paranoia Levels):

  • PL1 (По умолчанию): Базовый промышленный уровень. Обеспечивает защиту от подавляющего большинства типовых эксплойтов с минимальным риском ложных срабатываний. Подходит для WordPress, интернет-магазинов и корпоративных порталов.
  • PL2: Усиленный контроль заголовков и кодировок (дополнительная защита от обфускации и сложных SQLi). Рекомендуется при наличии времени на тестирование.
  • PL3 и PL4: Усиленный эвристический контроль для сред с повышенными требованиями к безопасности (платежные шлюзы, критическая инфраструктура). Требует предварительного профилирования приложения и составления обширного списка исключений, так как на этих уровнях блокируются многие нестандартные символы и специфические заголовки.

Если в процессе аудита в логах вы обнаружили, что легитимный запрос (например, сохранение записи в блоге через TinyMCE или Gutenberg) блокируется правилом 941100 (XSS Attack Detected via libinjection из семейства XSS-фильтров 941xxx), не следует отключать WAF. В SecLang предусмотрена тонкая настройка исключений.

В соответствии с канонической практикой OWASP Core Rule Set пользовательские правила-исключения (Rule Exclusions) подключаются ДО основного набора правил (Include @owasp_crs/*.conf) — аналогично тому, как в классическом ModSecurity используется файл предварительных исключений REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf. Это гарантирует, что директивы ctl:ruleRemoveById в первой фазе (phase:1) отработают до запуска инспекции сигнатур ядра CRS. Для Caddy директивы SecLang помещаются строго внутрь блока directives ` ... ` в секции coraza_waf вашего Caddyfile:

coraza_waf {
    load_owasp_crs
    directives `
        Include @coraza.conf-recommended
        Include @crs-setup.conf.example

        # Канонический порядок CRS: правила исключений размещаются ДО @owasp_crs/*.conf
        # Исключение 1: отключение ложно сработавшего правила 941100 только для панели WordPress
        SecRule REQUEST_URI "@beginsWith /wp-admin/" \
            "id:10001,phase:1,pass,nolog,ctl:ruleRemoveById=941100"

        # Исключение 2: отключение конфликтующего правила SQLi для эндпоинта вебхуков платежей
        SecRule REQUEST_URI "@streq /api/v1/payment-callback" \
            "id:10002,phase:1,pass,nolog,ctl:ruleRemoveById=942100"

        # Подключение основного ядра правил OWASP CRS v4
        Include @owasp_crs/*.conf
    `
}

⚠️ Предостережение по безопасности

Никогда не используйте директиву ctl:ruleEngine=Off для создания исключений на вебхуках или API-маршрутах. Полное отключение движка WAF оставляет эндпоинт абсолютно беззащитным перед SQL-инъекциями и эксплойтами. Всегда отключайте только конкретные конфликтующие ID правил через ctl:ruleRemoveById или целевые теги через ctl:ruleRemoveByTag.

💡 Совет инженера

Официальный регламент OWASP CRS резервирует для пользовательских локальных правил диапазон идентификаторов 1–99999 (на практике чаще всего используют пятизначные значения 10000–99999, чтобы визуально не пересекаться с системными кодами). Диапазон 900000–999999 строго зарезервирован ядром правил Core Rule Set, и совпадение ID приведет к синтаксической ошибке при парсинге конфигурации.

7. Боевое тестирование WAF: симуляция атак SQLi и Path Traversal

После подготовки конфигурационных файлов запустите контейнеры в фоновом режиме:

docker compose up -d --build

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

docker compose ps

Проверьте доступность приложения обычным HTTP-запросом:

curl -I http://localhost/

Сервер должен вернуть стандартный ответ HTTP/1.1 200 OK. Теперь протестируем реакцию фаервола на классическую SQL-инъекцию в строке запроса:

curl -i "http://localhost/?search=%27%20OR%201%3D1--"

Поскольку в конфигурации активен режим SecRuleEngine DetectionOnly, веб-сервер пропустит запрос и вернет 200 OK. Но если заглянуть в файл журнала аудита logs/audit.log, вы увидите запись с зафиксированной попыткой атаки.

Инженерное примечание о формате журнала: в SecLang формально декларируется директива SecAuditLogFormat JSON, однако в ряде версий движка Coraza при типе SecAuditLogType Serial события могут записываться в классическом строковом формате ModSecurity с разделителями секций (--a1b2c3d4-A--, --a1b2c3d4-B--, --a1b2c3d4-H--, см. открытый issue #1150 в трекере Coraza), либо в виде однострочного NDJSON при наличии структурированного адаптера. Приведенный ниже JSON-фрагмент иллюстрирует десериализованную объектную модель транзакции Coraza, к которой привыкли инженеры при экспорте данных в SIEM/ELK (отформатированную через jq):

{
  "transaction": {
    "timestamp": "17/Oct/2026:15:00:00 +0000",
    "unix_timestamp": 1792249200,
    "id": "179224920000.0001",
    "client_ip": "127.0.0.1",
    "client_port": 49210,
    "host_ip": "127.0.0.1",
    "host_port": 80,
    "request": {
      "method": "GET",
      "uri": "/?search=' OR 1=1--",
      "http_version": "HTTP/1.1",
      "headers": {
        "host": "localhost",
        "user-agent": "curl/8.5.0"
      }
    },
    "response": {
      "status": 200
    }
  },
  "messages": [
    {
      "message": "SQL Injection Attack Detected via libinjection",
      "data": {
        "rule_id": 942100,
        "file": "@owasp_crs/REQUEST-942-APPLICATION-ATTACK-SQLI.conf",
        "severity": "CRITICAL",
        "data": "matched payload: ' OR 1=1--"
      }
    }
  ]
}

Совет: Для потокового мониторинга инцидентов в консоли используйте команду tail -f logs/audit.log (или конвейер tail -f logs/audit.log | jq . при JSON-структуре).

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

SecRuleEngine On

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

docker compose exec waf caddy reload --config /etc/caddy/Caddyfile

Повторите тестовые вредоносные запросы. Сначала попытку внедрения SQL-кода:

curl -i "http://localhost/?search=%27%20OR%201%3D1--"

Затем атаку Path Traversal (обход директорий). Рекомендуется протестировать как канонический URL-кодированный payload (где %2E%2E%2F декодируется парсером WAF в ../), так и прямой запрос:

# Тест 1: Канонический URL-encoded запрос Path Traversal
curl -i "http://localhost/?file=%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2Fetc%2Fpasswd"

# Тест 2: Прямой запрос обхода путей
curl -i "http://localhost/?file=../../../../etc/passwd"

В обоих случаях Coraza WAF мгновенно заблокирует вредоносный запрос на границе инфраструктуры, вернув статус HTTP/1.1 403 Forbidden. Запрос даже не достигнет бэкенда, сберегая процессорное время и гарантируя неприкосновенность базы данных.

8. Чек-лист проверки и регламент эксплуатации сисадмина

Поскольку при формате SecAuditLogType Serial фаервол непрерывно дописывает структуры инцидентов в один файл, на боевом сервере необходимо настроить ротацию через системный logrotate, чтобы исключить риск переполнения диска. Создайте конфигурационный файл /etc/logrotate.d/coraza-waf на хост-системе:

/opt/coraza-waf/logs/audit.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    copytruncate
}

Директива copytruncate копирует текущий журнал и обнуляет оригинальный файл на лету, что позволяет ротировать логи без необходимости перезапуска контейнера и без блокировки файловых дескрипторов.

Компонент Команда / Действие Ожидаемый результат
Перезагрузка правил docker compose exec waf caddy reload Бесшовная перезагрузка на лету через Admin API (:2019)
Журнал инцидентов tail -f logs/audit.log Записи с деталями блокировок и rule_id
Ротация аудита logrotate -f /etc/logrotate.d/coraza-waf Сжатие логов с copytruncate без простоя сервиса
Сетевой периметр Проверка через онлайн сканер портов Открыты строго 80 и 443, бэкенд недоступен извне
Проверка SSL Проверка через онлайн чекер SSL Оценка A+, поддержка TLS 1.3 и HTTP/3

Усиление безопасности инфраструктуры на уровне веб-сервера

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

Вопросы и ответы (FAQ)

Популярные вопросы вебмастеров и сисадминов по данной теме

Влияет ли Coraza WAF на производительность и время ответа (TTFB) сайта?
В отличие от внешних облачных WAF, локальный Coraza WAF развертывается в едином инфраструктурном контуре с бэкендом и не вносит дополнительного сетевого плеча (RTT). Инспекция правил SecLang на базовом уровне паранойи PL1 выполняется в оперативной памяти внутри того же Go-процесса Caddy. Дополнительное процессорное время на разбор типовых заголовков и параметров GET/POST минимально и практически не отражается на пользовательском TTFB, обеспечивая при этом глубокую защиту от прикладных атак.
Почему рекомендуется связка с Caddy, а не ручная компиляция модуля для Nginx?
Модуль Coraza для Nginx (libcoraza) компилируется как динамическая библиотека под строго фиксированную версию веб-сервера. Любое системное обновление пакета nginx через apt upgrade приводит к падению сервера из-за несовместимости ABI. В связке с Caddy плагин coraza-caddy компилируется единожды в легковесный Docker-контейнер и работает автономно годами без риска сломаться при обновлении хост-системы.
Что делать, если WAF блокирует легитимные действия в админке или вебхуки?
Не отключайте WAF целиком. Откройте журнал аудита /var/log/coraza/audit.log, найдите идентификатор заблокированного правила (rule_id) и добавьте точечное исключение через SecRuleRemoveById или SecRuleUpdateTargetById только для целевого пути (например, /wp-admin/* или /api/v1/webhook). Это сохранит защиту остального периметра.

Читайте также в блоге SysKit

Смежные руководства и полезные технические статьи