Skip to main content

ModSecurity

Общая информация

Сайт проекта: https://modsecurity.org/

Анализ логов удобнее делать через AuditConsole. Однако похоже проект сдох. Возможности:

  • Централизация событий с помощью нескольких удаленных установок ModSecurity
  • Хранение и извлечение событий
  • Поддержка нескольких учетных записей пользователей и различных представлений
  • Тегирование событий
  • Правила событий, которые выполняются в консоли

Принципы modsecurity

  • Гибкость: мощный язык правил.
  • Пассивность: взаимодействие только по требованию
  • Предсказуемость
  • Качество выше количества
  • Функция "очистка трафика" бессмысленна

Два варианта работы: встроенный и reverse proxy. Первый вариант логичнее для односерверных проектов. Второй желательно запускать в режиме кластера прокси, но упрощает процесс управления.

Компоненты

Парсер Форматы данных используются анализаторами, которые извлекают фрагменты данных и сохраняют их для использования в правилах.
Буферизация При обычной установке буферизируется запрос и ответ. Важная функция, поскольку это единственный способ обеспечить надежную блокировку. Требует дополнительной ОП.
Логгирование Может сохранять весь HTTP-трафик.
Механизм правил К началу работы механизма правил, все необходимые фрагменты данных подготовлены. Правила оценивают транзакцию и предпринимают действия.

Жизненный цикл транзакции. Транзакция последовательно проходит 5 этапов:

Заголовки запроса Точка старта. Предоставляет создателям правил возможность быстрого первичного анализа запроса перед анализом тела запроса. Можно настроить, как будет анализироваться тело запроса.
Тело запроса Основные правила
Заголовки ответа Анализ происходит до получения тела ответа
Тело ответа
Логгирование Единственная фаза, в которой нельзя блокировать. Правила определяют, как и что нужно логгировать.

Пример запроса/ответа и связи с жизненным циклом транзакции. Простой запрос и ответ, правил нет.

POST /?a=test HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 6
b=test
HTTP/1.1 200 OK
Date: Sun, 17 Jan 2010 00:13:44 GMT
Server: Apache
Content-Length: 12
Connection: close
Content-Type: text/html
Hello World!

Процесс в контексте логов (похоже логи добавляются сверху):

Заголовки запроса

[4] Initialising transaction (txid SopXW38EAAE9YbLQ).
[5] Adding request argument (QUERY_STRING): name "a", value "test"
[4] Transaction context created (dcfg 8121800).
[4] Starting phase REQUEST_HEADERS.

Был создан контекст, добавлены аргументы и проинициализирована транзакция.

Тело запроса

[4] Second phase starting (dcfg 8121800).
[4] Input filter: Reading request body.
[9] Input filter: Bucket type HEAP contains 6 bytes.
[9] Input filter: Bucket type EOS contains 0 bytes.
[5] Adding request argument (BODY): name "b", value "test"
[4] Input filter: Completed receiving request body (length 6).
[4] Starting phase REQUEST_BODY.

Затем добавляются хуки фильтров

[4] Hook insert_filter: Adding input forwarding filter (r 81d0588).
[4] Hook insert_filter: Adding output filter (r 81d0588).

После этого:

[4] Input filter: Forwarding input: mode=0, block=0, nbytes=8192 (f 81d2228, r 81d0588).
[4] Input filter: Forwarded 6 bytes.
[4] Input filter: Sent EOS.
[4] Input filter: Input forwarding complete.

Заголовки ответа [9] Output filter: Receiving output (f 81d2258, r 81d0588).
[4] Starting phase RESPONSE_HEADERS.
Тело ответа

[9] Output filter: Bucket type MMAP contains 12 bytes.
[9] Output filter: Bucket type EOS contains 0 bytes.
[4] Output filter: Completed receiving response body (buffered full - 12 bytes).
[4] Starting phase RESPONSE_BODY.

Затем ответ отправляется клиенту

[4] Output filter: Output forwarding complete.

Логгирование [4] Initialising logging.
[4] Starting phase LOGGING.
[4] Audit log: Ignoring a non-relevant request.

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

Запуск Docker

Оказалось несколько сложнее. ИИ не дает полного ответа, нужно отстраивать по частям. Есть разные образы nginx-modsecurity.

Структура проекта: 

nginx-modsecurity/
├── Dockerfile
├── docker-compose.yml
├── nginx.conf
├── modsecurity.conf
├── nginx-logs/
├── modsec-logs/
└── html/
    └── index.html

Dockerfile

# ---------- СТАДИЯ 1: Сборка ModSecurity и nginx ----------
FROM ubuntu:22.04 AS builder

ENV DEBIAN_FRONTEND=noninteractive
ENV NGINX_VERSION=1.26.2

WORKDIR /opt

# Устанавливаем зависимости
RUN apt-get update && apt-get install -y \
    build-essential git automake autoconf libtool pkg-config \
    libxml2 libxml2-dev libyajl-dev libssl-dev zlib1g zlib1g-dev \
    libgeoip-dev liblmdb-dev libpcre2-dev \
    wget curl ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# ---------- Сборка libmodsecurity ----------
RUN git clone --depth 1 -b v3/master https://github.com/SpiderLabs/ModSecurity && \
    cd ModSecurity && \
    git submodule init && git submodule update && \
    ./build.sh && ./configure && make -j$(nproc) && make install

# ---------- Сборка nginx + ModSecurity connector ----------
RUN wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz && \
    tar xzf nginx-${NGINX_VERSION}.tar.gz && \
    git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git && \
    cd nginx-${NGINX_VERSION} && \
    ./configure --prefix=/usr/local/nginx \
        --with-compat \
        --add-dynamic-module=../ModSecurity-nginx && \
    make modules && make -j$(nproc) && make install

# ---------- СТАДИЯ 2: Финальный образ ----------
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive
ENV NGINX_VERSION=1.26.2
#WORKDIR /etc/nginx

RUN apt-get update && apt-get install -y \
    libyajl2 libxml2 libgeoip1 liblmdb0 ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# Копируем всё нужное из builder
COPY --from=builder /usr/local/modsecurity/ /usr/local/modsecurity/
COPY --from=builder /usr/local/nginx /usr/local/nginx
COPY --from=builder /opt/nginx-${NGINX_VERSION}/objs/ngx_http_modsecurity_module.so /usr/local/nginx/modules/

ENV PATH="/usr/local/nginx/sbin:${PATH}"

# Создаем необходимые директории
RUN mkdir -p /var/log/modsecurity \
    && mkdir -p /var/log/nginx \
    && mkdir -p /etc/modsecurity.d \
    && mkdir -p /usr/local/nginx/conf

# Копируем конфигурационные файлы, все равно потом перезапишем в compose
COPY nginx.conf /usr/local/nginx/conf/nginx.conf
COPY modsecurity.conf /etc/modsecurity.d/modsecurity.conf
COPY html /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

docker-compose.yml 

services:
  nginx-modsecurity:
    build: .
    container_name: nginx-modsecurity-ubnt
    ports:
      - "80:80"
    volumes:
      - ./html:/usr/local/nginx/html
      - ./nginx.conf:/usr/local/nginx/conf/nginx.conf
      - ./nginx-logs:/usr/local/nginx/logs
      - ./modsec-logs:/var/log/modsecurity
    restart: unless-stopped

nginx.conf 

load_module /usr/local/nginx/modules/ngx_http_modsecurity_module.so;

user  www-data;
worker_processes  auto;

events {
    worker_connections 1024;
}

http {
    include /usr/local/nginx/conf/mime.types;
    default_type application/octet-stream;

    # Директивы для логов (должны быть ДО server блоков)
    error_log /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;

    modsecurity on;
    modsecurity_rules_file /etc/modsecurity.d/modsecurity.conf;

    server {
        listen 80;
        server_name _;

        location / {
            root /usr/share/nginx/html;
            index index.html;
            modsecurity on;
        }

        location /status {
            access_log off;
            return 200 "Nginx on Ubuntu is working!\n";
            add_header Content-Type text/plain;
        }

       location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
            root /usr/share/nginx/html;
            modsecurity off;
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }
}

index.html 

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Nginx + ModSecurity</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
    </style>
</head>
<body>
    <h1>Nginx работает!</h1>

    <div>
        <h3>Тестовые ссылки:</h3>
        <ul>
            <li><a href="/">Нормальный запрос</a></li>
            <li><a href="/?test=malicious">Тестовая блокировка (сработает правило 1001)</a></li>
            <li><a href="/status">Статус страница</a></li>
        </ul>
    </div>
</body>
</html>

Запуск и тестирование 

# Запустите контейнер
docker compose up --build -d

# Проверьте логи
docker compose logs -f

# Тестовые запросы
curl http://localhost/
curl "http://localhost/?test=<script>alert('xss')</script>"

modsecurity.conf в разделе Настройка modescurity

Структура файла настроек

Файл настройки разбит на следующие блоки (логически)

  • Общие настройки
  • Настройки аудита
  • Дополнительные настройки
  • Правила

Можно распределить настройки по файлам, указав в основном файле ссылки на дополнительные файлы.

Общие настройки

Параметр Описание Необходимость
Основной 

SecRuleEngine Включает модуль 

DetectionOnly - только логгирование (но лимиты все равно работают)

On - включена и блокировка

 

SecRuleEngine On
+
SecDefaultAction Действие по умолчанию в случае соответствия фильтрам. 
SecDefaultAction "phase:1,log,auditlog,pass"
+/-
Настройки запроса

SecRequestBodyAccess Буферизация тела запроса. Если отключить, то не будет доступа к post параметрам.
SecRequestBodyAccess On
-
SecRequestBodyLimit Максимальный размер тела запроса. Устаревший параметр.
SecRequestBodyLimit 134217728
-
SecRequestBodyLimitAction

Действие при превышении лимита тела запроса

ProcessPartial продолжить без буферизации

Reject запретить

-
SecRequestBodyInMemoryLimit Максимальный размер тела запроса (не понял отличия) Возможно, лимит буферизации в ОП или на диске -
SecRequestBodyNoFilesLimit Максимальный размер тела запроса исключая размер файла -
Настройки ответа

SecResponseBodyAccess Буферизация тела ответа. Часто можно установить в Off, поскольку известны ответы.
SecResponseBodyAccess On

SecResponseBodyLimit

SecResponseBodyLimitAction

Как для запроса

SecResponseBodyMimeType

Список MIME типов для анализа 
SecResponseBodyMimeType text/plain text/html

SecResponseBodyMimeTypesClear

ХЗ
Хранение данных

SecDataDir Папка хранения постоянных данных 
SecDataDir /tmp/
+
SecTmpDir Папка хранения временных данных 
SecTmpDir /tmp/
+
SecUploadDir Размещение закачанных файлов
SecUploadKeepFiles Включить / выключить сохранение файлов
SecUploadFileMode Права на сохраненные файлы 
SecUploadFileMode 0600

SecUploadFileLimit Ограничение на количество файлов в одном запросе

Настройки логгирования

Параметр Описание Необходимость
Лог отладки

SecDebugLog Файл сохранения лога отладки +
SecDebugLogLevel Уровень предупреждений. Хватает 3. 0-отключено, 9-максимально -
Лог данных

SecAuditEngine

On логгировать все

RelevantOnly только попавшие под правила

Off не логгировать

+
SecAuditLog

Файл лога

+
SecAuditLogRelevantStatus

Статус ответа, попадающего под лог. Например 

SecAuditLogRelevantStatus "^(?:5|4(?!04))"
-
SecAuditLogParts

A Заголовок события (время, уникальный ID и т.д.)
B Заголовки HTTP-запроса
C Тело запроса (POST data, JSON, XML и т.п.)
E Внутреннее тело ответа (редко нужно)
F Заголовки HTTP-ответа
G Само тело ответа (HTML, JSON и т.п.)
H  Итоговая информация: какие правила сработали, теги, сообщение и т.д.
I Подробности внутреннего состояния движка
J  Содержимое/метаданные загруженных файлов
K  Все сработавшие правила (включая нефатальные)
Z  Маркер конца записи (всегда нужен)

SecAuditLogParts ABIJEFHZ

 

-
SecAuditLogType

Serial Все логи в один файл — последовательно (по умолчанию).
Concurrent Каждое событие в отдельный файл, а общий индекс (audit.log) ведётся отдельно. Удобно, когда нужно параллельное логирование.
HTTPS Отправляет аудит-логи по HTTP/HTTPS на удалённый сервер, обычно в систему централизованного логирования (SIEM, ELK и т.д.).

-

Дополнительные настройки

Параметр Описание
SecArgumentSeparator Устанавливает разделитель в application/x-www-form-urlencoded  По умолчанию & но иногда бывает и другое
SecArgumentSeparator &
SecCookieFormat Версия парсера cookies. 0 или 1. 0 - по умолчанию - более чем достаточно.

Пример минимального файла без правил.

# Basic Configuration
SecRuleEngine On
SecRequestBodyAccess On
SecResponseBodyAccess Off

# Audit Log Settings
SecAuditEngine On
SecAuditLogType Serial
SecAuditLog /var/log/modsecurity/audit.log

# Debug Settings
SecDebugLog /var/log/modsecurity/debug.log
SecDebugLogLevel 9

# Temporary directories
SecTmpDir /tmp/
SecDataDir /tmp/

Настройка отладки

Для nginx нет возможности разделить настройку в стиле тегов в пределах файла конфигурации modsecurity. Можно разделить только на уровне web сервера. 

location /myapp/ {
        modsecurity on;
        modsecurity_rules_file /usr/local/nginx/modsecurity/modsecurity_debug.conf;
        root /usr/local/nginx/html;
    }

То есть это отдельный файл с собственными настройками. Вариант динамического изменения уровня в правилах тоже не работает в 3 версии. 

Возможен анализ User-Agent 

SecRule REQUEST_HEADERS:User-Agent YOUR_UNIQUE_ID phase:1,nolog,pass,ctl:debugLogLevel=9

 

 

Правила modsecurity

SecRule VARIABLES OPERATOR TRANSFORMATION

VARIABLES Точки запроса, к которым применяется правило.
OPERATOR Регулярное выражение для поиска в VARIABLES.
TRANSFORMATION Действия в случае совпадения

Переменные (VARIABLES)

  • REQUEST_URI: URI запроса;
  • ARGS: параметры запроса;
  • REQUEST_BODY: тело запроса;
  • REQUEST_COOKIES - куки;
  • REQUEST_HEADERS - заголовки;
  • XML - XML код в запросе.и т.д.

Операторы (OPERATOR)

  • @contains: содержит указанное значение;
  • @rx: совпадает с регулярным выражением;
  • @eq: равно указанному значению и другие.

Модификаторы (TRANSFORMATION)

SecRule ARGS "password" "@rx (?i)password" "id:2,phase:2,t:none,t:lowercase,t:urldecode,t:replaceComments"

  • t:none: Отключить все стандартные преобразования;
  • t:lowercase: Преобразовать в нижний регистр;
  • t:urldecode: Декодировать URL;
  • t:replaceComments: Удалить комментарии.

Дополнительные опции

SecRule REQUEST_METHOD "@streq POST" "id:3,phase:2,chain,t:none,pass,nolog"

SecRule REQUEST_URI "@rx /login" "t:none,t:urldecode,t:replaceComments"

  • chain: Связывает два правила так, что следующее применяется только если предыдущее сработало;
  • pass: Продолжить выполнение правил вне зависимости от результата текущего;
  • nolog: Не записывать событие в лог.

Примеры правил ModSecurity

Пример 1:

SecRule ARGS "password" "@rx (?i)password" "id:123,deny,status:403,msg:'Password leakage detected'"

ARGS: обращение к параметрам запроса
@rx: оператор совпадения с регулярным выражением
id: идентификатор правила
deny: действие с запросом - запретить
status: HTTP-статус ответа при срабатывании правила - 403
msg: название правила - 'Password leakage detected'

Данное правило ищет в параметрах запроса "password" и срабатывает при его обнаружении.

Пример 2:
SecRule ARGS "@rx ^(?:\'|\"|%27|%22|%3E|%3C|%3D|%3B|%20or%20|union|select|insert|update|delete|drop)" \

"id:1,phase:2,deny,msg:'SQL Injection Attack'"

Правило ищет SQL-инъекции в параметрах запроса (ARGS) и блокирует запрос, если обнаруживается соответствие с паттерном.

Пример 3:
SecRule REQUEST_COOKIES|REQUEST_HEADERS|ARGS|XML:/* \

"(<|%3C)([^sS]*s[sS]*c[cC]*r[rR]*i[iI]*p[pP]*t[tT]*)(>|%3E)" \

"id:2,phase:2,t:none,deny,msg:'Cross-site Scripting (XSS) Attack'"

Правило обнаруживает попытки вставки скриптов в запросы (XSS) и блокирует их.

Пример 4:
SecRule FILES_TMPNAMES "@inspectFile /etc/passwd" \

"id:3,phase:2,t:none,deny,msg:'Attempt to access /etc/passwd'"

Правило анализирует временные файлы, загруженные на сервер, и блокирует запрос, если в содержимом обнаруживается попытка доступа к файлу /etc/passwd.

Пример 5:
SecRule RESPONSE_BODY "@contains 'Invalid password'" \

"id:5,phase:4,t:none,log,deny,msg:'Password brute force attempt'"

Правило обнаруживает попытки подбора пароля по сообщению об ошибке в теле ответа и блокирует соответствующий запрос.

Как писать правила ModSecurity

ModSecurity Core Rule Set (CRS) — это набор правил, разработанный сообществом Open Web Application Security Project (OWASP) для использования с ModSecurity. Эти правила предназначены для обеспечения базовой защиты от различных видов популярных веб-атак. Вам не всегда нужно создавать свои собственные правила, так как CRS уже предоставляет обширный набор правил, охватывающих множество сценариев атак.

Но если у организации есть специфические требования или вы хотите настроить правила под свои нужды, вы можете создавать собственные правила, в соответствии со следующим алгоритмом:

1. Определите цель правила
Определите, какую атаку или вид активности вы хотите обнаруживать.
Решите, на какую часть HTTP-запроса (например, заголовки, тело запроса, параметры запроса) должно применяться правило.
2. Создайте правило
Определите фазу, на которой будет выполняться правило (например, phase:1 или phase:2).

Используйте документацию ModSecurity для создания правила, например:

SecRule REQUEST_HEADERS:User-Agent "@contains badbot" "id:1001,phase:2,deny,msg:'Bad Bot Detected.'" 

                  
Это правило запрещает запросы с User-Agent, содержащим строку "badbot" в заголовках.

3. Тестируйте правило
Включите правило и протестируйте его - нужно убедиться, что правило срабатывает на атаки и не фолсит.
Используйте инструменты для тестирования веб-безопасности (например, burp suit), чтобы проверить, как правило реагирует на различные виды запросов.
Если правило срабатывает не так, как хотелось бы, или блокирует легитимные запросы, исправьте его, чтобы снизить ложные срабатывания.
Просматривайте логи ModSecurity для выявления проблем и улучшения правил.
4. Документируйте
Добавьте комментарии к правилу, объясняющие его цель и предназначение.
Ведите документацию, чтобы другие коллеги могли легко понять, почему правило было создано.
Другой пример правила, предназначенного для блокировки SQL-инъекций:

SecRule ARGS|ARGS_NAMES|REQUEST_HEADERS|XML:/* "@rx (?i:(?:\b(?:union\s*all|select\s*(?:.+)\s*from|create\s*(?:.+)\s*table|delete\s*from|drop\s*(?:.+)\s*table|exec\s*(?:\w+\s*|\s*)\(|insert\s*into|shutdown|update\s*(?:.+)\s*set)\b))" "phase:2,deny,status:403,id:1002,msg:'SQL Injection attempt.'" 

Это правило проверяет параметры запроса, заголовки и XML-данные на наличие попыток SQL-инъекций.