Набор скриптов ConfigServer Security & Firewall (CSF) изначально обладает достаточно богатыми возможностями по организации защиты сервера хостинга Web с помощью фильтра пакетов iptables. В частности с его помощью можно противостоять затоплению атакуемого хоста пакетами TCP SYN, UDP и ICMP слабой и средней силы. Дополняет CSF встроенный Login Failure Daemon (lfd), который осуществляет мониторинг журналов на предмет наличия многочисленных неудачных попыток авторизации в различных сетевых сервисах с целью подбора пароля. Такие попытки блокируются путем внесения адреса IP злоумышленника в черный список CSF. 

Существует другой сторонний инструмент, реализующий аналогичный функционал: Fail2ban. Несмотря на схожесть решаемых задач, между lfd и Fail2ban присутствует кардинальное отличие. Первый имеет закрытую архитектуру и поддерживает ограниченный набор сервисов. В то время как второй позволяет самостоятельно разработать фильтры практически под любые задачи. Однако сожительствуют CSF и Fail2ban в пределах одного сервера плохо, поскольку обращаются с правилами iptables несколько бесцеремонно. Постараемся решить эту проблему на примере ОС Linux Debian v7.XX amd64 так, что бы извлечь максимум из возможностей обоих инструментов. А в качестве примера организуем защиту от атак DDoS на nginx.

В моей конфигурации CSF был изначально установлен и настроен на сервере. Останавливаться на этом вопросе подробно я не буду, материала по нему достаточно. Рекомендую внести следующие настройки в файл "/etc/csf/csf.conf":

SYNFLOOD = "1"
SYNFLOOD_RATE = "100/s"
SYNFLOOD_BURST = "10"

CONNLIMIT = "80;110,443;110"

PORTFLOOD = "80;tcp;20;1,443;tcp;20;1"

CT_LIMIT = "300"
CT_INTERVAL = "60"



Такая конфигурация позволит CSF противодействовать тем хостам, которые задействованы в атаке затопления пакетами TCP SYN или открывают большое количество подключений к портам TCP сервера HTTP.
Переходим к установке Fail2ban, по окончании которой его необходимо остановить и отключить автозапуск:
 

service fail2ban stop
update-rc.d -f fail2ban remove



Запускать Fail2ban мы будем средствами CSF. Для этого необходимо создать скрипт "/etc/csf/csfpost.sh":

#!/bin/sh
/etc/init.d/fail2ban reload



Он же обеспечит автоматическую загрузку правил Fail2ban в iptables, если CSF будет перезагружать свои, например, в случае обновления.
Идея интеграции Fail2ban с CSF строится на том, что для блокировки адресов IP злоумышленников первый будет использовать черный список второго, а не напрямую правила iptables. Однако полностью от iptables в Fail2ban мы не отказываемся.
Отключаем все фильтры в Fail2ban. Большую их часть перекрывает lfd. Fail2ban будем использовать только для того, что не поддерживает lfd.

sed -i "s|enabled  = true|enabled  = false|g" /etc/fail2ban/jail.conf



Добавляем поддержку CSF в Fail2ban. Для этого создадим файл настроек "/etc/fail2ban/action.d/csf-ip-deny.conf" следующего содержания:

[Definition]
actionstart =
actionstop =
actioncheck =
actionban = csf -d <ip> Added by Fail2Ban for <name>
actionunban = csf -dr <ip>



Заменим для всех фильтров Fail2ban действие блокировки на созданный «csf-ip-deny»:

sed -i -e "s|banaction = |banaction = csf-ip-deny\n#banaction = |" /etc/fail2ban/jail.conf



Для особо назойливых злоумышленников предусмотрена длительная блокировка. Реализуется этот механизм путем контроля журнала самого Fail2ban. Создаем файл настроек "/etc/fail2ban/filter.d/fail2ban.conf":

[Definition]
# Count all bans in the logfile
failregex = fail2ban.actions: WARNING \[(.*)\] Ban <HOST>
# Ignore our own bans, to keep our counts exact.
# In your config, name your jail 'fail2ban', or change this line!
ignoreregex = fail2ban.actions: WARNING \[fail2ban\] Ban <HOST>



Добавляем в "/etc/fail2ban/jail.conf" следующие строки:

## fail2ban with CSF to block repeat offenders
[fail2ban]
enabled  = true
filter   = fail2ban
action   = iptables-allports
#          sendmail-whois[name=fail2ban]
logpath  = /var/log/fail2ban.log
maxretry = 10
# Find-time: 1 day
findtime = 86400
# Ban-time: 1 week
bantime = 604800



Т.е. те из злоумышленников, кто в течение суток блокировался другими фильтрами 10 и более раз, будет заблокирован этим фильтром на неделю. Обратите внимание, что тут используется действие «iptables-allports», а не «csf-ip-deny». Это не ошибка. Так нужно, что бы длительная блокировка не оказалась случайно снятой другими фильтрами.

Теперь на примере организации защиты nginx от атак DDoS рассмотрим создание правил фильтрации Fail2ban. Начнем с настройки nginx, в нем необходимо задействовать возможности модулей ngx_http_limit_conn_module иngx_http_limit_req_module. Для этого добавляем следующие строки в настройки:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn perip 100;
limit_conn_zone $server_name zone=perserver:10m;
limit_conn perserver 200;
limit_req_zone $binary_remote_addr zone=reqip:10m rate=10r/s;
limit_req zone=reqip burst=30;



Таким образом, мы установили лимиты на не более чем 100 подключений с одного адреса IP единовременно со скоростью 10-30 новых подключений в секунду и не более 200 – к одному сайту всего с любого количества адресов IP. Превышения будет фиксироваться в журнале ошибок, для которого мы настроим фильтры Fail2ban.
Создаем следующие файлы настроек.
"/etc/fail2ban/filter.d/nginx-conn-limit.conf"

# Fail2Ban configuration file
#

[Definition]
# Option:  failregex
# Notes.:  Regexp to catch a generic call from an IP address.
# Values:  TEXT
#
failregex = limiting connections by zone.*client: <HOST>

# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =



"/etc/fail2ban/filter.d/nginx-req-limit.conf"

# Fail2Ban configuration file
#
 
[Definition]
# Option:  failregex
# Notes.:  Regexp to catch a generic call from an IP address.
# Values:  TEXT
#
failregex = limiting requests.*client: <HOST>
 
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =



"/etc/fail2ban/filter.d/nginx-dos.conf"

# Fail2Ban configuration file
#

[Definition]
# Option:  failregex
# Notes.:  Regexp to catch a generic call from an IP address.
# Values:  TEXT
#
failregex = ^<HOST> -.*"(GET|POST).*HTTP.*"$

# Option:  ignoreregex
# Notes.:  regex to ignore. If this regex matches, the line is ignored.
# Values:  TEXT
#
ignoreregex =



Добавляем в "/etc/fail2ban/jail.conf":

[nginx-conn-limit]
enabled = true
filter = nginx-conn-limit
action = csf-ip-deny[name=nginx-conn-limit]
logpath = /var/log/nginx/error.log
maxretry = 4
findtime = 21600
bantime = 3600
 
[nginx-req-limit]
enabled = true
filter = nginx-req-limit
action = csf-ip-deny[name=nginx-req-limit]
logpath = /var/log/nginx/error.log
maxretry = 4
findtime = 21600
bantime = 3600

[nginx-dos]
# Based on apache-badbots but a simple IP check (any IP requesting more than
# 240 pages in 60 seconds, or 4p/s average, is suspicious)
enabled = true
filter  = nginx-dos
action = csf-ip-deny[name=nginx-dos]
logpath = /var/log/nginx/access.log
maxretry = 240
findtime = 60
bantime  = 3600



Фильтры «nginx-conn-limit» и «nginx-req-limit» будут блокировать тех, кто превышает лимиты на подключения к nginx, а «nginx-dos» – тех, кто слишком часто обращается к сайтам: более 240 страниц в минуту.

Часто целью атак является широкой распространенная CMS WordPress: перебор паролей и большое количество запросовXML-RPC. Организуем защиту от них тоже.
"/etc/fail2ban/filter.d/nginx-wp-login.conf"

[Definition]
failregex = <HOST> .*POST /wp-login.php
ignoreregex =



"/etc/fail2ban/filter.d/nginx-wp-xmlrpc.conf"

[Definition]
failregex = <HOST> .*POST /xmlrpc.php
ignoreregex =



"/etc/fail2ban/filter.d/nginx-wp-register.conf"

[Definition]
failregex = ^<HOST> .* "GET /wp-login.php\?action=register HTTP/.*" .*$
ignoreregex =



Добавляем в "/etc/fail2ban/jail.conf":

[nginx-wp-login]
enabled = true
filter = nginx-wp-login
action = csf-ip-deny[name=nginx-wp-login]
logpath = /var/log/nginx/access.log
maxretry = 4
findtime = 600
bantime = 3600
 
[nginx-wp-xmlrpc]
enabled = true
filter = nginx-wp-xmlrpc
action = csf-ip-deny[name=nginx-wp-xmlrpc]
logpath = /var/log/nginx/access.log
maxretry = 4
findtime = 600
bantime = 3600

[nginx-wp-register]
enabled = true
filter = nginx-wp-register
action = csf-ip-deny[name=nginx-wp-register]
logpath = /var/log/nginx/access.log
maxretry = 4
findtime = 600
bantime = 3600



Вот, что можно будет наблюдать в журнале Fail2ban во время атаки DDoS на nginx:

spoiler



А вот так будут блокироваться чрезмерно назойливые злоумышленники:

spoiler



В последнее время участились случаи, когда злоумышленники осуществляют распределенный перебор паролей с разных адресов IP одной подсети класса C. Алгоритм работы Fail2ban не способен распознать такое поведение:

spoiler



Устраним этот недостаток с помощью следующего скрипта, который будет выполняться каждый час планировщиком cron.
"/etc/cron.hourly/fail2ban-subnets"

#!/bin/bash

log="/var/log/fail2ban.log"
limit=30
grep=`which grep`

${grep} "fail2ban.actions.*Ban" ${log} | ${grep} -E -o "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" | awk -F'.' '{print $1"."$2"."$3}' | sort -u | while read line
    do
        count=$(${grep} -c "fail2ban.actions.*Ban.*${line}" ${log})
            if [ ${count} -ge ${limit} ]
                then
                    /usr/sbin/csf -td ${line}.0/24 7d "Subnet ${line}.0/24 is blocked for a week by Fail2ban after ${count} attempts"
            fi
    done

exit 0



Т.е. сети IP класса C будут заблокированы на неделю целиком, если ранее другие фильтры Fail2ban срабатывали на адреса из них 30 или более раз.
Вот так выглядит результат:

# csf -t

A/D   IP address                               Port   Dir   Time To Live     Comment
DENY  193.176.147.0/24                           *    in    6d 21h 34m 18s   Subnet 193.176.147.0/24 is blocked for a week by Fail2ban after 641 attempts
DENY  46.148.30.0/24                             *    in    6d 21h 34m 19s   Subnet 46.148.30.0/24 is blocked for a week by Fail2ban after 332 attempts
DENY  46.148.31.0/24                             *    in    6d 21h 34m 19s   Subnet 46.148.31.0/24 is blocked for a week by Fail2ban after 334 attempts