«Только запустил 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)


  1. UncleAndy
    22.12.2022 17:45
    +1

    Спасибо за статью! Использую fail4ban как обязательный инструмент для защиты от брутфорса по ssh. Подключение nginx по вашей схеме, думаю, тоже будет полезным.


    1. edo1h
      25.12.2022 05:23

      Использую fail4ban как обязательный инструмент для защиты от брутфорса по ssh.

      не проще поставить нормальные пароли? а лучше вовсе запретить парольную авторизацию в ssh


  1. 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>,
    ?


    1. is113 Автор
      23.12.2022 19:00
      +1

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

      В конкретно этом случае соглашусь, допустимо указать Ваш вариант.


  1. HaMiL
    22.12.2022 18:29
    +3

    Такая защита морально устарела и поможет на совсем уж простых ботнетах. Можно ее "наворачивать" но гибкости не получится. Как вариант, рассмотреть уже более продвинутый варианты где для блокировок используются наполняемые вредоносными тредами базы. Например, crowdsec или bitninja


    1. is113 Автор
      23.12.2022 19:01
      +1

      Crowdsec - действительно хорошее open source 'продолжение' fail2ban. А bitninja только в полных платных тарифах даёт защиту от Dos.

      Несомненно, стоит оценивать критичность защищаемого сервиса и использовать более эффективные распределенные системы очистки трафика


  1. vitaly_il1
    23.12.2022 08:46

    Будем реалистами - от brute force (подбор паролей) это помогает, от DDoS - не смешите.
    Без "внешней" фильтрации с DDoS не справиться.


    1. ptr128
      23.12.2022 10:20
      +1

      Не совсем так. Если речь о веб-приложении, которое работает исключительно через TLS и реагирует только на TCP трафик 443 порта, то средства fail2ban дают ощутимый эффект. Даже довольно крупные ботнеты быстро оказываются в клетке. Но, к сожалению, в клетке оказываются внешние IP адреса. То есть, клиенты за NAT, в котором так же находится бот, оказываются заблокированными вместе с ботом. Раньше это было неприятно. Сейчас, в связи с более широким распространением IPv6, уже терпимо.


      1. jenki
        23.12.2022 20:34
        +1

        Вам никогда не заливали по несколько гигабит UDP или HRE трафика? Не попадали под wordpress ping с тысячи хостов? А как fail2ban потребляет ресурсы? Он легко по пожиранию ресурсов переплёвывает целевые приложения на сервере. Так же знаменит своими ложными срабатываниями, иногда превращающих сервер в недоступную вещь в себе. Поэтому требует пристального и постоянного внимания.
        В настройках nginx есть настройки, позволяющие блонировать нежелательный трафик. А в связке с Lua это можно делать более гибко.


        1. ptr128
          23.12.2022 20:37

          На сервер, где закрыты все порты, кроме одного TCP, в веб-приложение, которое просто обеспечивает REST API и принимает исключительно авторизованных клиентов? Нет, не заливали )


          1. ColdPhoenix
            25.12.2022 10:07

            Принимающие порты почти не важны. Вам просто забивают канал до всего вашего стека.

            fail2ban способен защитить от слабого L7-DDoS.


            1. ptr128
              25.12.2022 11:09

              Давайте по порядку. Если речь о перегрузке входящего канала, то это вообще не моя забота, а ЦОД, перекрывающего ответы на широковещательные ICMP или UDP пакеты. Моя забота лишь дропать как UDP, так и ICMP на файрволле и не забыть о net.ipv4.tcp_syncookies = 1.

              Для того, чтобы перегрузить сервер, нужно вообще сначала авторизоваться.

              Для того, чтобы перегрузить исходящий канал, надо чтобы размер моего ответа был существенно больше, чем размер запроса. Начинается это с размера HELLO, которое у сервера намного больше, чем у клиента. Но так как это уже рукопожатие TLS, то к этому моменту fail2ban становится применим.


  1. ptr128
    23.12.2022 10:05

    Asterisk хорошо fail2ban прикрывал еще этак в 2005-2006 году.


  1. Dr_Wut
    23.12.2022 20:47

    У меня только один вопрос - а вы серьезно пишете про защиту от DDoS уже на клиентском порту в 2022 году? Даже не смешно, вы вроде как компания авторитетная...


    1. idmrty
      24.12.2022 18:33

      А ничего смешного нет. Они пишут про атаки на L7, и fail2ban — вполне нормальный способ из «бесплатных». L2—L4 действительно фильтруются выше в нормальных ЦОД — статья не об этом.


      1. edo1h
        25.12.2022 05:27

        да не нормальный он. слишком тяжеловесный, плюс сама идея блокировки по анализу логов выглядит плохо.
        единственный плюс: его можно прикрутить почти ко всему, что шевелится пишет логи.
        но использовать fail2ban в связке с nginx, серьёзно?