«Только запустил 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
Стенд после включения Fail2ban.
Установка и базовая настройка — лишь первый этап в защите веб-приложения. Самое главное — правильно настроить правила блокировки IP-адресов для конкретных видов атак.
Возможно, эти тексты тоже вас заинтересуют:
→ Open source, собственные серверы и экспертиза: доступный межсетевой экран для инфраструктуры в Selectel
→ Как просканировать сетевой периметр сервиса с помощью open source-инструментов
→ Anonymous покушаются на бургеры. DDoS-2022: ждать ли новые серии атак
Специальная настройка от 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
Таким образом можно настраивать защиту от брута как на все ресурсы, так и на отдельные страницы с разными условиями бана.
А какие средства защиты веб-серверов используете вы? Поделитесь своим опытом и мнением в комментариях.
Комментарии (16)
Mausglov
22.12.2022 17:46+3Не первый раз встречаю, как пишут детальное регулярное выражение, и не понимаю, зачем?
например:failregex = ^\s*[[a-z]+] \d+#\d+: *\d+ limiting requests, excess: [\d.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>,
Почему бы не записать проще:failregex = .+ limiting requests, excess: .+ client: <HOST>,
?is113 Автор
23.12.2022 19:00+1В регулярках хороший вариант - описывать подробно шаблон выражения. Слишком широкое выражение допускает намного больший разброс совпадающих значений. В случае с большим количеством логов могут быть пересечения в найденных логах.
В конкретно этом случае соглашусь, допустимо указать Ваш вариант.
HaMiL
22.12.2022 18:29+3Такая защита морально устарела и поможет на совсем уж простых ботнетах. Можно ее "наворачивать" но гибкости не получится. Как вариант, рассмотреть уже более продвинутый варианты где для блокировок используются наполняемые вредоносными тредами базы. Например, crowdsec или bitninja
is113 Автор
23.12.2022 19:01+1Crowdsec - действительно хорошее open source 'продолжение' fail2ban. А bitninja только в полных платных тарифах даёт защиту от Dos.
Несомненно, стоит оценивать критичность защищаемого сервиса и использовать более эффективные распределенные системы очистки трафика
vitaly_il1
23.12.2022 08:46Будем реалистами - от brute force (подбор паролей) это помогает, от DDoS - не смешите.
Без "внешней" фильтрации с DDoS не справиться.ptr128
23.12.2022 10:20+1Не совсем так. Если речь о веб-приложении, которое работает исключительно через TLS и реагирует только на TCP трафик 443 порта, то средства fail2ban дают ощутимый эффект. Даже довольно крупные ботнеты быстро оказываются в клетке. Но, к сожалению, в клетке оказываются внешние IP адреса. То есть, клиенты за NAT, в котором так же находится бот, оказываются заблокированными вместе с ботом. Раньше это было неприятно. Сейчас, в связи с более широким распространением IPv6, уже терпимо.
jenki
23.12.2022 20:34+1Вам никогда не заливали по несколько гигабит UDP или HRE трафика? Не попадали под wordpress ping с тысячи хостов? А как fail2ban потребляет ресурсы? Он легко по пожиранию ресурсов переплёвывает целевые приложения на сервере. Так же знаменит своими ложными срабатываниями, иногда превращающих сервер в недоступную вещь в себе. Поэтому требует пристального и постоянного внимания.
В настройках nginx есть настройки, позволяющие блонировать нежелательный трафик. А в связке с Lua это можно делать более гибко.ptr128
23.12.2022 20:37На сервер, где закрыты все порты, кроме одного TCP, в веб-приложение, которое просто обеспечивает REST API и принимает исключительно авторизованных клиентов? Нет, не заливали )
ColdPhoenix
25.12.2022 10:07Принимающие порты почти не важны. Вам просто забивают канал до всего вашего стека.
fail2ban способен защитить от слабого L7-DDoS.
ptr128
25.12.2022 11:09Давайте по порядку. Если речь о перегрузке входящего канала, то это вообще не моя забота, а ЦОД, перекрывающего ответы на широковещательные ICMP или UDP пакеты. Моя забота лишь дропать как UDP, так и ICMP на файрволле и не забыть о net.ipv4.tcp_syncookies = 1.
Для того, чтобы перегрузить сервер, нужно вообще сначала авторизоваться.
Для того, чтобы перегрузить исходящий канал, надо чтобы размер моего ответа был существенно больше, чем размер запроса. Начинается это с размера HELLO, которое у сервера намного больше, чем у клиента. Но так как это уже рукопожатие TLS, то к этому моменту fail2ban становится применим.
Dr_Wut
23.12.2022 20:47У меня только один вопрос - а вы серьезно пишете про защиту от DDoS уже на клиентском порту в 2022 году? Даже не смешно, вы вроде как компания авторитетная...
idmrty
24.12.2022 18:33А ничего смешного нет. Они пишут про атаки на L7, и fail2ban — вполне нормальный способ из «бесплатных». L2—L4 действительно фильтруются выше в нормальных ЦОД — статья не об этом.
edo1h
25.12.2022 05:27да не нормальный он. слишком тяжеловесный, плюс сама идея блокировки по анализу логов выглядит плохо.
единственный плюс: его можно прикрутить почти ко всему, чтошевелитсяпишет логи.
но использовать fail2ban в связке с nginx, серьёзно?
UncleAndy
Спасибо за статью! Использую fail4ban как обязательный инструмент для защиты от брутфорса по ssh. Подключение nginx по вашей схеме, думаю, тоже будет полезным.
edo1h
не проще поставить нормальные пароли? а лучше вовсе запретить парольную авторизацию в ssh