В типовой конфигурации веб-сервера безопасность сайта ограничивается связкой фаервола 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 Гбит/с.
Selectel — Надежные облачные серверы
Изолированная инфраструктура в защищенных дата-центрах Tier III, выделенные vCPU и гибкое управление приватными подсетями.
Beget — Простота для веб-мастеров
Мгновенный деплой Docker-образов в 1 клик, наглядный мониторинг системных ресурсов, автоматические ежедневные бэкапы и отзывчивый саппорт 24/7.
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.