Укрепление Nginx с помощью Fail2ban: тестируем и оцениваем «профит»
Показываем, как с помощью open source-инструмента Fail2ban укрепить Nginx и защитить сервисы от взлома.
«Только запустил Nginx-приложение, а в access.log уже десятки записей с непонятными запросами, переборами паролей и другого» — типичная ситуация для незащищенного сервера. Это следствие работы «ботов» для сканирования белых сетей, поиска открытых портов, сбора информации о версиях запущенных служб и подбора паролей к сервисам.
В статье расскажу, как с помощью open source-инструмента Fail2ban укрепить Nginx и защитить сервисы от взлома. А также продемонстрирую способы блокирования DDoS-атак.
Базовая защита в Nginx
Nginx — это не только быстрый и гибкий веб-сервер, но и довольно безопасный, если правильно его настроить. Рассмотрим пару примеров.
Защита от Clickjacking. С помощью встроенного параметра X-Frame-Options в HTTP-ответе веб-сервера можно задать разрешение страницы в фреймы и iframe — и свести вероятность уязвимости Clickjacking к минимуму.
Защита от DDoS. Если правильно «откалибровать» размеры клиентского буфера, можно снизить влияние DDoS-атак. Также с помощью команд deny и allow можно настроить доступ к приложению с определенных IP-адресов и сетей.
Но подобные способы защиты эффективны не во всех случаях. Например, во время интенсивной DDoS-атаки вручную блокировать адреса сложно. Проще настроить автоматическую фильтрацию источников обращений при выполнении определенных условий.
В случае с Bruteforce эффективней настроить временную блокировку пользователя — либо на уровне логики приложения, либо на обратном прокси-сервере. Если используется базовая аутентификация Nginx, сделать это еще проще.
Для автоматизированной отработки атак лучше использовать специальный софт — в нашем случае это Fail2ban. Но для начала давайте посмотрим, что будет, если атаковать беззащитный сервер.
Атака незащищенного веб-сервера
В рамках статьи рассмотрим два вида атак, в которых промежуточным звеном будет Nginx, а целью хакера — некое веб-приложение.
Для демонстрации я развернул такой стенд: Fail2ban пока что отключен и даже не установлен. Зато есть Wazuh, который помогает мониторить события безопасности.
Wazuh — это комплексная open source-платформа для обеспечения мониторинга безопасности. С помощью нее можно:
- закрыть потребность в полноценном SIEM-решении,
- обеспечить реагирование на инциденты,
- собирать логи со множества систем и аналитику корреляции событий.
Благодаря встроенным шаблонам Wazuh позволяет быстро оценивать соответствие системы требованиям различных стандартов в области ИБ. Наиболее подробно установка и базовая настройка описаны в официальной документации.
Посмотрим, что будет, если атаковать Nginx-сервер без защиты Fail2ban.
Имитация DoS-атаки
Суть атаки сводится к тому, чтобы «забить» канал связи бессмысленным трафиком и исчерпать ресурсы сервера. При этом нелегитимные запросы должны отработать до такой степени, чтобы было невозможно обработать легитимные. В качестве такой атаки будем рассматривать флуд HTTP-запросами — для этого используем Siege. Он отлично подходит для стресс-тестов HTTP-приложений.
Нагрузим сервер запросами в 100 конкурентных пользователей.
root@kali:~# siege -t 1m -c 100 http://192.168.100.3/admin
** SIEGE 4.0.4
** Preparing 100 concurrent users for battle.
The server is now under siege...
Lifting the server siege...
Transactions: 206090 hits
Availability: 100.00 %
Elapsed time: 59.83 secs
Data transferred: 40.13 MB
Response time: 0.03 secs
Transaction rate: 3444.59 trans/sec
Throughput: 0.67 MB/sec
Concurrency: 99.05
Successful transactions: 206271
Failed transactions: 0
Longest transaction: 0.32
Shortest transaction: 0.00
В мониторинге видим график, содержащий большое количество запросов.
Непроизводительный веб-сервер на таком количестве запросов может ощутимо загрузить процессор. Запустив параллельно несколько источников запроса, можно и вовсе «положить» сервер.
Имитация Bruteforce
Атака предполагает перебор пар логин-пароль по заранее определенному словарю, который содержит часто используемые комбинации. Рассмотрим перебор данных для базового типа аутентификации Nginx, который может быть настроен, например, для административных страниц.
root@kali:~# curl http://192.168.100.3/admin
<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.14.0 (Ubuntu)</center>
</body>
</html>
Аутентификация на странице 192.168.100.3/admin настроена. При обращении к ней обычный пользователь получает 401 ошибку.
На хосте для атаки создадим списки с логинами и паролями соответственно. С помощью инструмента patator и модуля http_fuzz «забрутим» страницу с базовой аутентификацией Nginx.
Результат — «всплески» в разделе Security Events мониторинга Wazuh.
Для Bruteforce все пары логин-пароль были обработаны сервером Nginx, блокирования не произошло. Если бы хактевист потратил больше времени на перебор, сервис был бы взломан.
Последствия Bruteforce и DoS-атак могут быть критичными для реального проекта. Поэтому попробуем подключить и настроить Fail2ban — решение, которое может защитить веб-сервер от потенциальных простоев, утечек данных и другого.
Установка и базовая настройка Fail2ban
Fail2ban — это open source-приложение для фильтрации подключений. С помощью него можно создать специальную «клетку» jail, куда будут записываться вредоносные IP-адреса из iptables, и установить, например, сроки блокирования.
Установим и настроим пакет Fail2ban — он есть в стандартных репозиториях Ubuntu, поэтому можно воспользоваться менеджером пакетов.
sudo apt install fail2ban -y
После выполнения команды в каталоге /etc/fail2ban/ появятся файлы конфигурации.
admin@nginx:~# ll /etc/fail2ban/
total 92
drwxr-xr-x 6 root root 4096 Dec 13 17:50 ./
drwxr-xr-x 86 root root 4096 Dec 11 18:10 ../
drwxr-xr-x 2 root root 4096 Nov 25 05:56 action.d/
-rw-r--r-- 1 root root 2334 Jan 18 2018 fail2ban.conf
drwxr-xr-x 2 root root 4096 Apr 4 2018 fail2ban.d/
drwxr-xr-x 3 root root 4096 Dec 13 17:30 filter.d/
-rw-r--r-- 1 root root 22959 Dec 11 19:11 jail.conf
drwxr-xr-x 2 root root 4096 Dec 11 19:03 jail.d/
-rw-r--r-- 1 root root 645 Jan 18 2018 paths-arch.conf
-rw-r--r-- 1 root root 2827 Jan 18 2018 paths-common.conf
-rw-r--r-- 1 root root 573 Jan 18 2018 paths-debian.conf
-rw-r--r-- 1 root root 738 Jan 18 2018 paths-opensuse.conf
Из этого списка нас интересуют следующие файлы:
- /etc/fail2ban/jail.conf — содержит нативные параметры для поиска совпадений в логах.
- /etc/fail2ban/filter.d/nginx-limit-req.conf — нужен для отслеживания количества обращений к странице и скорости ее обработки. Может пригодиться для блокирования DoS- и bruteforce-атак.
- /etc/fail2ban/filter.d/nginx-http-auth.conf — нужен для обнаружения и блокирования bruteforce-атак на базовую аутентификацию Nginx.
По умолчанию в конфиге Fail2ban такие настройки рассматриваемых модулей:
[nginx-http-auth]
port = http,https
logpath = %(nginx_error_log)s
[nginx-limit-req]
port = http,https
logpath = %(nginx_error_log)s
Блокирование атак с помощью Fail2ban
Установка и базовая настройка — лишь первый этап в защите веб-приложения. Самое главное — правильно настроить правила блокировки IP-адресов для конкретных видов атак.
Специальная настройка от DoS-атак
Подготовка nginx-limit-req. Попробуем защитить веб-сервер с помощью модуля nginx-limit-req. Он нужен для определения максимального всплеска запросов (burst) и пороговых значений разделяемой памяти (zone). В случае, если эти параметры будут превышены, модуль добавит в лог-файл новые записи. С помощью них Fail2ban определяет и блокирует вредоносные IP-адреса.
Описания ключевых параметров из файлов конфигураций можно найти по ссылке.
Модуль nginx-limit-req входит в nginx-core, и дополнительная его установка не потребуется. В директиве http добавим параметры для включения модуля и определения зоны:
limit_req_zone $binary_remote_addr zone=one:10m rate=5r/s;
Далее в директиве location достаточно добавить значение для ограничения количества запросов:
limit_req zone=one burst=5;
Теперь при превышении количества запросов, который мы задали в burst, в /var/log/nginx/error.log будут сыпаться ошибки следующего вида:
2022/12/14 18:39:28 [error] 9462#9462: *987284 limiting requests, excess: 1.940 by zone "one", client: 192.168.100.1, server: localhost, request: "GET /admin HTTP/1.1", host: "192.168.100.3"
Теперь мы можем искать в логах шаблонные сообщения. Именно их Fail2ban будет «искать» в логе nginx и при достижении определенного количества выполнять действие — блокирование источника атаки.
Подготовка jail. В файле /etc/fail2ban/jail.local зададим следующие значения:
[nginx-limit-req]
port = http,https
enabled = true
logpath = /var/log/nginx/*error.log
findtime = 10
bantime = 30
maxretry = 5
В фильтрах Fail2ban /etc/fail2ban/filter.d/nginx-limit-req.conf нужно записать следующее регулярное выражение для параметра failregex:
[Definition]
ngx_limit_req_zones = [^"]+
failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>,
Регулярное выражение позволяет найти строку определенного формата в логах nginx. Как можем видеть, некоторые слова являются постоянными, а часть значений — переменными. Они заданы специальным синтаксисом регулярного выражения.
Результат. После включения Fail2ban и запуска атаки видим следующие записи:
admin@nginx:~# tail -n 5 /var/log/fail2ban.log
2022-12-14 18:30:39,505 fail2ban.filter [9237]: INFO [nginx-limit-req] Found 192.168.100.1 - 2022-12-14 18:30:39
2022-12-14 18:30:39,506 fail2ban.filter [9237]: INFO [nginx-limit-req] Found 192.168.100.1 - 2022-12-14 18:30:39
2022-12-14 18:30:39,508 fail2ban.filter [9237]: INFO [nginx-limit-req] Found 192.168.100.1 - 2022-12-14 18:30:39
2022-12-14 18:30:39,509 fail2ban.filter [9237]: INFO [nginx-limit-req] Found 192.168.100.1 - 2022-12-14 18:30:39
2022-12-14 18:30:39,960 fail2ban.actions [9237]: NOTICE [nginx-limit-req] Ban 192.168.100.1
Обратите внимание: вредоносный источник превысил параметры nginx-limit-req и попал в клетку jail. Атакующий хост получил сообщение о прекращении сессии.
root@kali:~# siege -t 1m http://192.168.100.3/admin
================================================================
WARNING: The number of users is capped at 255. To increase this
limit, search your .siegerc file for 'limit' and change
its value. Make sure you read the instructions there...
================================================================
** SIEGE 4.0.4
** Preparing 255 concurrent users for battle.
The server is now under siege...ERROR from http_request
ERROR from http_request
ERROR from http_request
ERROR from http_request
[error] socket: unable to connect sock.c:249: Connection refused
ERROR from http_request
ERROR from http_request
ERROR from http_request
siege aborted due to excessive socket failure; you
can change the failure threshold in $HOME/.siegerc
Transactions: 0 hits
Availability: 0.00 %
Elapsed time: 1.20 secs
Data transferred: 0.27 MB
Response time: 0.00 secs
Transaction rate: 0.00 trans/sec
Throughput: 0.23 MB/sec
Concurrency: 6.71
Successful transactions: 7
Failed transactions: 1278
Longest transaction: 0.20
Shortest transaction: 0.00
В Wazuh также виден «всплеск» обращений и их резкое прекращение — Fail2ban заблокировал нелегитимные запросы.
Специальная настройка от Bruteforce
На хосте уже установлен Fail2ban. Создадим файл jail.local и добавим в него настройки для базовой аутентификации.
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/*error.log
findtime = 10
bantime = 30
maxretry = 3
Переводя на человеческий, можно сказать так:
- после трех неудачных попыток авторизации будут блокироваться порты http (80/TCP) и https (443/TCP),
- Fail2ban мониторит лог-файл /var/log/nginx/error.log,
- поиск совпадений в лог-файле длится 10 секунд,
- вредоносный адрес блокируется на 30 секунд,
- Fail2ban блокирует адрес, если тот совпадает с шаблоном минимум 3 раза.
В файле /etc/fail2ban/filter.d/nginx-http-auth.conf оставили значение регулярного выражения по умолчанию:
admin@nginx:~# cat /etc/fail2ban/filter.d/nginx-http-auth.conf
# fail2ban filter configuration for nginx
[Definition]
failregex = ^ \[error\] \d+#\d+: \*\d+ user "(?:[^"]+|.*?)":? (?:password mismatch|was not found in "[^\"]*"), client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(?:, referrer: "\S+")?\s*$
При необходимости в этот файл можно добавлять свои регулярные выражения. Их Fail2ban также будет использовать для поиска совпадений.
Результат. Запустим тот же брутфорс, что и в начале статьи.
Первый пик — действие Bruteforce до включения защиты: Nginx обработал весь список пар из логинов паролей. Отсутствие данных на определенных промежутках обусловлено 30-секундной блокировкой вредоносных IP-адресов. По истечении времени часть адресов продолжает «брутфорсить» страницу авторизации. Но все пары из логинов паролей не успевают обрабатываться — отсюда и «затухающие» пики.
В логах Fail2ban записано следующее:
admin@nginx:~# tail -n 5 /var/log/fail2ban.log
2022-12-13 17:34:58,340 fail2ban.filter [21234]: INFO [nginx-http-auth] Found 192.168.100.1 - 2022-12-13 17:34:58
2022-12-13 17:34:58,340 fail2ban.filter [21234]: INFO [nginx-http-auth] Found 192.168.100.1 - 2022-12-13 17:34:58
2022-12-13 17:34:58,340 fail2ban.filter [21234]: INFO [nginx-http-auth] Found 192.168.100.1 - 2022-12-13 17:34:58
2022-12-13 17:34:58,341 fail2ban.filter [21234]: INFO [nginx-http-auth] Found 192.168.100.1 - 2022-12-13 17:34:58
2022-12-13 17:34:58,471 fail2ban.actions [21234]: NOTICE [nginx-http-auth] Ban 192.168.100.1
Обратите внимание: запросы авторизации регистрировались одновременно, и Fail2ban даже не успевал учитывать некоторые из них. Но в результате все равно находил совпадения и блокировал вредоносные адреса. Напомню, действие происходило в рамках одного L2-домена.
Fail2ban блокировал источники путем добавления адресов в цепочку Chain f2b-nginx-http-auth, которая в свою очередь добавлена в INPUT:
admin@nginx:~# sudo iptables -L -v
Chain INPUT (policy ACCEPT 2312 packets, 123K bytes)
pkts bytes target prot opt in out source destination
<b>3517 492K f2b-nginx-http-auth tcp -- any any anywhere anywhere multiport dports http,https</b>
2521 178K f2b-sshd tcp -- any any anywhere anywhere multiport dports ssh
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 2294 packets, 604K bytes)
pkts bytes target prot opt in out source destination
Chain f2b-nginx-http-auth (1 references)
pkts bytes target prot opt in out source destination
<b>172 10320 REJECT all -- any any 192.168.100.1 anywhere reject-with icmp-port-unreachable</b>
2594 437K RETURN all -- any any anywhere anywhere
Обратите внимание: Fail2ban автоматически описал правила iptables для блокирования исходящего трафика от атакующего IP.
А на стороне атакующего получали следующие результаты атаки Bruteforce:
17:54:38 patator INFO - code size:clen time | candidate | num | mesg
17:54:38 patator INFO - -----------------------------------------------------------------------------
17:54:58 patator FAIL - xxx 92:-1 0.000 | admin:123123 | 2 | Unexpected HTTP response
17:55:00 patator FAIL - xxx 92:-1 0.000 | admin:12345 | 4 | Unexpected HTTP response
Это как следствие того, что сервер отклонял обращения на порт 80/TCP с адреса атакующего хоста:
root@kali:~# curl http://192.168.100.3/admin
curl: (7) Failed to connect to 192.168.100.3 port 80: Connection refused
Таким образом можно настраивать защиту от брута как на все ресурсы, так и на отдельные страницы с разными условиями бана.