Когда я впервые столкнулся с массированной атакой на свой SSH-сервер, в логах мелькали десятки тысяч попыток подбора паролей за час. Стандартные настройки безопасности оказались бесполезными против распределённого ботнета. Именно тогда я понял: Fail2Ban нужно настраивать не поверхностно, а глубоко, создавая собственные фильтры под каждый тип атаки. И результат превзошёл ожидания.

Архитектура Fail2Ban: три кита защиты

Fail2Ban работает по принципу непрерывного мониторинга логов через три взаимосвязанных компонента. Первый уровень составляют фильтры в директории /etc/fail2ban/filter.d/, содержащие регулярные выражения failregex для обнаружения подозрительных паттернов. Второй уровень это jail-ы, которые связывают фильтр с конкретным лог-файлом и определяют параметры срабатывания: maxretry (число попыток), findtime (временное окно) и bantime (длительность блокировки). Третий уровень представляют actions в /etc/fail2ban/action.d/, исполняющие команды блокировки через iptables, nftables, firewalld или кастомные скрипты.

Критически важный момент: никогда не редактируйте дефолтные файлы jail.conf или filter.conf напрямую. При обновлении пакета все изменения будут потеряны. Вместо этого создавайте файлы с расширением .local или помещайте конфигурации в каталоги jail.d/ и filter.d/. Система Fail2Ban автоматически применит ваши настройки поверх дефолтных, сохранив их при любых обновлениях.

Основной механизм работы выглядит так: Fail2Ban использует backend для чтения логов (polling, systemd, pyinotify), применяет regex-фильтры к каждой строке, подсчитывает совпадения для каждого IP-адреса в окне findtime и при превышении maxretry запускает actionban. После истечения bantime выполняется actionunban и IP освобождается. Вся информация о банах записывается в /var/log/fail2ban.log, который позже используется фильтром recidive.

SSH: создание агрессивных фильтров

Стандартный фильтр sshd охватывает базовые случаи неудачной аутентификации, но современные атаки требуют более изощрённого подхода. Фильтр sshd поддерживает режимы через параметр mode: normal, ddos, extra и aggressive. Чтобы активировать агрессивный режим, в секции jail укажите:

[sshd]
enabled = true
filter = sshd[mode=aggressive]
logpath = /var/log/auth.log
maxretry = 3
bantime = 1h
findtime = 10m

Агрессивный режим включает дополнительные паттерны, такие как блокировка по быстрым отключениям на этапе preauth. Эти отключения часто сигнализируют о сканировании портов или автоматизированных проверках уязвимостей. Строка в логе выглядит примерно так: "Connection closed by authenticating user root 192.168.1.100 port 52314 [preauth]". Дефолтный режим пропускает такие события, но они критически важны для раннего обнаружения.

Для создания собственного расширенного фильтра создайте файл /etc/fail2ban/filter.d/sshd-custom.local:

[Definition]
failregex = ^%(__prefix_line)sFailed password for .* from <HOST>.*$
            ^%(__prefix_line)sFailed \S+ for (?P<cond_inv>invalid user )?(?P<user>\S+) from <HOST>
            ^%(__prefix_line)sInvalid user .* from <HOST>
            ^%(__prefix_line)sConnection closed by authenticating user .* <HOST> port \d+ \[preauth\]$
            ^%(__prefix_line)sReceived disconnect from <HOST>: 11: Bye Bye
            ^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers
ignoreregex =

Здесь используется тег <HOST>, который Fail2Ban автоматически заменяет на regex для IPv4 и IPv6. Переменная __prefix_line подставляет стандартный префикс лог-строки с датой и временем. Обратите внимание на именованные группы типа (?P<user>\S+), которые позволяют извлекать дополнительную информацию для логирования.

Критически важно понимать флаги в regex-файлах. В документации Fail2Ban упоминаются флаги типа F-MLFID (multi-line filter ID), F-NOFAIL (не считать как failure) и другие, которые управляют внутренней логикой обработки. Флаг <F-MLFID> позволяет создавать многострочные паттерны для сложных атак, когда признаки взлома распределены по нескольким строкам лога.

Для тестирования созданного фильтра используйте команду:

fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd-custom.local --print-all-matched

Эта команда покажет все совпавшие строки и извлечённые IP-адреса. Если видите ноль совпадений при наличии атак в логе, значит regex требует корректировки.

Защита веб-приложений: WordPress и не только

Веб-приложения атакуются совершенно иначе. Здесь нет чётких сообщений "Failed password", вместо этого нужно анализировать HTTP-коды ответов, паттерны URL-запросов и поведенческие аномалии. Для WordPress основные векторы атак это брутфорс wp-login.php и эксплуатация xmlrpc.php для DDoS-усиления.

Создайте фильтр /etc/fail2ban/filter.d/wordpress-auth.conf:

[Definition]
failregex = ^<HOST> -.*"(POST|GET).*(wp-login\.php).*" (200|401|403)
            ^<HOST> -.*"(POST|GET).*(xmlrpc\.php).*" (200|401|403)
ignoreregex =

Обратите внимание: здесь мы ловим код 200, потому что WordPress возвращает успешный HTTP-ответ даже при неудачном логине. Страница просто перезагружается с ошибкой. Если вы используете плагины безопасности типа Wordfence или Limit Login Attempts, они могут логировать неудачные попытки с кодом 403, что упрощает детектирование.

Для более агрессивной защиты создайте фильтр от сканеров уязвимостей. Эти боты постоянно ищут .env файлы, директории .git, phpMyAdmin и другие типичные точки входа:

[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*(/\.env|/\.git|/phpmyadmin|/adminer|/wp-config\.php\.bak).*"
            ^<HOST> -.*"(GET|POST).*(\.\./\.\./|\%2e\%2e/|union.*select|script>).*"
maxretry = 1

Здесь maxretry установлен в 1, потому что легитимный пользователь никогда не обращается к этим путям. Единственный запрос к .env это уже признак сканирования.

Продвинутая техника: защита от HTTP POST-флуда через анализ логов ошибок Nginx. Если вы настроили limit_req_zone в Nginx, превышения лимита записываются в error.log:

[Definition]
failregex = limiting requests, excess:.* by zone ".*", client: <HOST>

Jail для этого фильтра может выглядеть так:

[nginx-limit-req]
enabled = true
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
findtime = 30
bantime = 6h

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

Ban Actions: от iptables до интеграции с облаками

Стандартное действие Fail2Ban это создание правил firewall через iptables-multiport или iptables-allports. При запуске службы Fail2Ban создаёт отдельную цепочку правил для каждого активного jail, например fail2ban-sshd. Это элегантное решение не засоряет основные правила firewall и легко откатывается при остановке службы.

Действие iptables-multiport блокирует только указанные порты:

iptables -I fail2ban-sshd 1 -s 192.168.1.100 -j REJECT

Действие iptables-allports изолирует IP полностью:

iptables -I fail2ban-sshd 1 -s 192.168.1.100 -j DROP

Разница между REJECT и DROP в том, что REJECT отправляет ICMP-пакет обратно, информируя об отказе, а DROP молча игнорирует пакеты. Для атакующих DROP предпочтительнее, так как не даёт информации о работающем firewall.

Для веб-серверов за Cloudflare стандартные iptables-действия бесполезны, так как вы видите только IP Cloudflare, а не реального атакующего. Решение: использовать API Cloudflare для блокировки. Создайте файл /etc/fail2ban/action.d/cloudflare.conf:

[Definition]
actionban = curl -s -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/firewall/access_rules/rules" \
            -H "X-Auth-Email: <CLOUDFLARE_EMAIL>" \
            -H "X-Auth-Key: <API_KEY>" \
            -H "Content-Type: application/json" \
            --data '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"Banned by Fail2Ban"}'

actionunban = curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/firewall/access_rules/rules/<ID>" \
              -H "X-Auth-Email: <CLOUDFLARE_EMAIL>" \
              -H "X-Auth-Key: <API_KEY>"

Теги <ip>, <name>, <matches> автоматически заменяются Fail2Ban при исполнении. Это обеспечивает универсальность действий.

Другой полезный вариант: интеграция с AbuseIPDB для репортинга атакующих IP. Это помогает другим администраторам и улучшает глобальную базу угроз. В action добавьте:

actionban = curl -s "https://api.abuseipdb.com/api/v2/report" \
            -H "Key: <API_KEY>" -H "Accept: application/json" \
            --data-urlencode "ip=<ip>" --data-urlencode "categories=18,22"

Recidive: многоуровневая защита от настойчивых

Механизм recidive это метаджейл, отслеживающий IP, которые уже были забанены другими jail-ами. Технически он мониторит собственный лог Fail2Ban в /var/log/fail2ban.log и ищет строки вида "[jail-name] Ban <HOST>". Стандартный фильтр recidive.conf содержит regex:

[Definition]
failregex = ^%(__prefix_line)s(?:\s*fail2ban\.actions\s*%(__pid_re)s?:\s+)?NOTICE\s+\[<_jailname>\]\s+Ban\s+<HOST>

Критически важно исключить сам recidive из отслеживания, иначе возникнет петля. В regex используется тег <_jailname>, который должен быть настроен на игнорирование самого себя.

Типичная конфигурация recidive:

[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
banaction = iptables-allports
bantime = 1w
findtime = 1d
maxretry = 5

Это означает: если IP был забанен 5 раз за последние сутки в любых jail-ах, заблокировать его на неделю по всем портам. Для супер-агрессивных атакующих можно создать второй уровень recidive-long с параметрами bantime = 4w, findtime = 1w, maxretry = 3.

Продвинутая техника: перманентные баны через ipset и blocklist. Создайте action, который при срабатывании recidive добавляет IP не только в iptables, но и в постоянный файл:

echo "<ip>" >> /etc/fail2ban/ip.blocklist.recidive
ipset add fail2ban-permanent <ip>

При старте системы загружайте этот список:

while read ip; do ipset add fail2ban-permanent "$ip"; done < /etc/fail2ban/ip.blocklist.recidive

Важный нюанс: если уровень логирования установлен в DEBUG, сам процесс бана recidive создаёт записи в лог, что может привести к петле. Установите loglevel = INFO или WARNING в fail2ban.local.

Современные версии Fail2Ban (0.11+) поддерживают bantime.increment для автоматического увеличения времени бана без recidive:

[DEFAULT]
bantime.increment = true
bantime.factor = 1
bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor

Эта формула удваивает время бана при каждом повторном нарушении, ограничивая максимум 20 итерациями. Первый бан 1 час, второй 2 часа, третий 4 часа и так далее.

Тестирование regex: проверяй дважды

Самая распространённая ошибка создание regex без тестирования на реальных логах. Команда fail2ban-regex позволяет прогнать фильтр по логу и увидеть все совпадения:

fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-auth.conf

Добавьте флаг --print-all-missed для отображения строк, которые regex пропустил. Это критически важно для отладки.

Опасность плохих regex: если удалённый пользователь может внедрить текст, который совпадёт с failregex и захватит часть <HOST>, он получит возможность заблокировать любой IP по своему выбору. Поэтому всегда закрепляйте regex на текст, генерируемый приложением, а не пользователем. Используйте ^ в начале и $ в конце для полного соответствия строке.

Предпочитайте неалчные совпадения .*? вместо .*, особенно перед тегом <HOST>. Алчное совпадение захватывает максимум символов, что может привести к неправильному извлечению IP, если в строке несколько адресов.

После создания конфигураций проверьте их работу:

fail2ban-client reload
fail2ban-client status
fail2ban-client status sshd

Симулируйте атаку с тестового хоста и проверьте, появился ли IP в списке забаненных. Используйте fail2ban-client set sshd banip 192.168.1.100 для ручного бана при тестировании actions.

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