РУКОВОДСТВО ПО БЕЗОПАСНОСТИ СЕРВЕРОВ

Fail2ban не банит IP: как найти и исправить причину

Коротко: если fail2ban работает, но никого не банит, поломка почти всегда в одном из четырёх мест: jail не включён или не загрузился, он читает не тот лог или журнал, его фильтр не совпадает со строками лога, либо бан создаётся, но действие firewall не даёт рабочего правила. Проверяйте в этом порядке. Сначала выполните sudo fail2ban-client status sshd: если счётчик неудач стоит на нуле, пока входы падают, проблема в источнике лога или фильтре; если счётчик банов растёт, а атакующие всё равно проходят, проблема в применении бана. В конце проверьте всё тестовым баном на документационном адресе.

Это руководство по диагностике дополняет наше руководство по настройке fail2ban, где разобраны установка и тонкая настройка jail. Здесь мы считаем, что инструмент установлен, и выясняем, почему он молчит.

Быстрый результат (10 минут)

  • Выполните sudo fail2ban-client status и убедитесь, что ваш jail есть в списке jail.
  • Выполните sudo fail2ban-client -t, чтобы проверить, что конфигурация загружается без ошибок.
  • Выполните sudo fail2ban-client get sshd logpath (или get sshd journalmatch) и убедитесь, что источник действительно содержит строки SSH.
  • Запустите sudo fail2ban-regex на этом логе с --print-all-missed, чтобы увидеть, какие неудачные входы фильтр пропускает.
  • Забаньте вручную 203.0.113.9, найдите адрес в правилах firewall и снимите бан.

Статус jail делит проблему пополам

Начните с вывода fail2ban-client status для вашего jail: его счётчики показывают, какая половина конвейера сломана. У jail две половины: обнаружение (читать лог, находить неудачи, считать их) и применение (выполнить действие, меняющее firewall). Большинство жалоб «не банит» связаны со сбоем только в одной из них.

sudo fail2ban-client status
sudo fail2ban-client status sshd

Статус отдельного jail показывает список отслеживаемых файлов (или условий журнала), счётчики неудач (Currently failed, Total failed) и счётчики банов (Currently banned, Total banned). Читайте их так:

  • Jail нет в списке jail: он отключён или не смог загрузиться. Переходите к следующему разделу.
  • Total failed остаётся 0, а в auth-логе есть неудачные входы: jail «слепой». Либо он читает не тот источник, либо фильтр не совпадает. Смотрите разделы про источник лога и фильтр.
  • Total failed растёт, а Total banned остаётся 0: неудачи видны, но не доходят до порога, либо нарушитель в исключениях. Смотрите пороги и ignoreip.
  • Total banned растёт, но атакующий всё равно подключается: бан записан, но не применяется. Смотрите раздел про применение бана.

Страница руководства fail2ban-client(1) описывает status с необязательным именем jail и вариантом вывода. Держите эту развилку в голове: она избавит вас от проверки firewall, когда настоящая причина в фильтре, и наоборот.

Работает ли сервис и включён ли jail?

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

sudo systemctl status fail2ban
sudo fail2ban-client ping
sudo fail2ban-client status

В руководстве ping описан как проверка того, что сервер жив. Если сервис упал или перезапускается, посмотрите причину командой sudo journalctl -u fail2ban -n 50 (см. нашу статью про journalctl) и запустите проверку конфигурации:

sudo fail2ban-client -t

Параметр -t проверяет конфигурацию, а синтаксическая ошибка или неверный параметр в любом файле не даёт применить новые настройки. Поставляемый jail.conf задаёт enabled = false в значениях по умолчанию и поясняет, что по умолчанию все jail отключены, поэтому каждому нужному jail требуется enabled = true в файле, которым управляете вы. Согласно jail.conf(5), файлы .local читаются после файлов .conf, и побеждает значение, прочитанное позже, поэтому свои переопределения кладите в jail.local или jail.d/*.local:

# /etc/fail2ban/jail.local
[sshd]
enabled = true

Примените изменение без полного перезапуска командой sudo fail2ban-client reload и снова выполните status. Чтобы увидеть, что jail реально использует после объединения всех файлов, спросите его напрямую, а не гадайте:

sudo fail2ban-client get sshd maxretry
sudo fail2ban-client get sshd findtime
sudo fail2ban-client get sshd bantime
sudo fail2ban-client get sshd actions

Каждая из этих команд get описана в руководстве клиента. Устройство jail разобрано в статье глоссария про jail.

Тот ли лог или журнал читает jail?

Jail считает только те неудачи, которые видит, поэтому проверьте, что именно он читает. Спросите у fail2ban, а не доверяйте файлу, который вы правили:

sudo fail2ban-client get sshd logpath
sudo fail2ban-client get sshd journalmatch

logpath перечисляет файлы, которые отслеживает jail, а journalmatch показывает фильтр журнала. Затем сами откройте этот источник и убедитесь, что в нём есть строки аутентификации SSH за последний час. Jail, указывающий на пустой, отсутствующий или переставший обновляться файл, будет показывать ноль неудач вечно.

Файловый backend или systemd

Согласно jail.conf(5), backend по умолчанию равен auto: он пробует pyinotify раньше polling и может переключиться на журнал, только если journalmatch задан для jail или его фильтра. Backend systemd читает журнал, и, как сказано на странице, указывать logpath для него недопустимо: вместо этого используется journalmatch. В upstream-файле путей для Debian sshd_log указывает на /var/log/auth.log, а sshd_backend равен systemd. Поэтому на хосте, где sshd пишет только в журнал, файловый jail ничего не читает. Решение: явно указать backend.

[sshd]
enabled = true
backend = systemd

Проверьте, что в журнале есть то, что ожидает jail:

sudo journalctl -u ssh -u sshd --since "1 hour ago" -g "Failed|Invalid"

В руководстве journalctl(1) описаны -u для юнита, --since для времени и -g для регулярного выражения по сообщению.

Имя юнита: ssh или sshd

Служба SSH называется по-разному в разных системах. В документации Ubuntu Server используется ssh.service, а фильтр sshd, поставляемый с fail2ban, перечисляет в journalmatch значения _SYSTEMD_UNIT=sshd.service, _COMM=sshd и _COMM=sshd-session. Поставляемый фильтр поэтому совпадает ещё и по имени процесса, но собственный journalmatch, скопированный из руководства для другого дистрибутива, у вас может не совпасть ни с чем. Сверьтесь командой systemctl list-units --type=service | grep -i ssh и исправьте имя юнита в своём переопределении.

Пути, которые изменились или никогда не существовали

По jail.conf(5), шаблон в logpath охватывает только файлы, существующие на момент запуска fail2ban. Если лог создаётся позже, переехал на новый путь после обновления или приложение теперь пишет в другое место, перезапустите или перезагрузите fail2ban после исправления пути и снова проверьте через get logpath. Если вы ротируете собственный лог fail2ban, клиент описывает flushlogs как команду, которая сбрасывает файловую цель лога и открывает её заново для ротации.

Совпадает ли фильтр со строками лога?

Если источник верный, а Total failed всё равно ноль, фильтр не узнаёт ваши строки. Проверьте фильтр на реальных данных с помощью fail2ban-regex: он никого не банит. По странице руководства fail2ban-regex(1), он принимает лог (строку, имя файла или systemd-journal) и фильтр (строку, имя фильтра или jail, либо путь к файлу фильтра).

# лог-файл
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf --print-all-missed

# журнал systemd (нужен systemd-python)
sudo fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf --print-all-missed

--print-all-missed печатает каждую строку, которую фильтр не распознал, а --print-all-matched печатает распознанные. Ищите среди пропущенных строк то, что явно является неудачным входом. Если такие строки есть, фильтр и формат лога расходятся, и это ваша причина. Типичные поводы:

  • Строки пришли от другого демона или в другом формате, чем ждёт фильтр, например после обновления OpenSSH или при нестандартном LogLevel. По умолчанию LogLevel в sshd_config(5) равен INFO.
  • У фильтра есть режимы. Поставляемый фильтр sshd описывает normal (по умолчанию), ddos, extra и aggressive, выбираемые параметром mode в jail или в имени фильтра, например sshd[mode=aggressive]. Прочитайте комментарии в установленном файле фильтра, чтобы узнать, что считает каждый режим.
  • Дату в строке не удаётся разобрать, или она в неожиданном формате. --verbose-date показывает, как определялись даты, а -d задаёт собственный шаблон даты.

Если источник это журнал, параметр -m (--journalmatch) позволяет передать условия в стиле journalctl, переопределяющие файл фильтра: так можно быстро проверить исправленное имя юнита, не трогая jail. Повторяйте ту же команду после каждого исправления. Переходить дальше стоит, только когда фильтр находит совпадения на реальных неудачных входах.

Не уверены, что ваши баны доходят до firewall?

Бесплатная проверка сервера только для чтения оценивает открытые сервисы и состояние firewall, чтобы декоративный jail не остался незамеченным.

Получить бесплатную проверку сервера Связаться с инженером по безопасности

Пороги, ignoreip и часы

Когда неудачи считаются, а бана нет, смотрите на числа и исключения. В поставляемом upstream-файле jail.conf bantime равен 10m, findtime равен 10m, а maxretry равен 5, но ваш дистрибутив или ваши файлы могут отличаться, поэтому читайте живые значения через get sshd maxretry и get sshd findtime. Согласно jail.conf(5), maxretry это число неудач, которые должны произойти за findtime до текущего момента, чтобы адрес был забанен.

Отсюда следует, что медленный сканер, пробующий пароль раз в несколько минут, может держаться под любым порогом, а слишком большой maxretry не срабатывает на коротких всплесках. Как подбирать эти значения, объясняет наше руководство по настройке; здесь задача лишь убедиться, что они такие, как вы думаете. Об ограничении числа попыток на стороне SSH см. MaxAuthTries.

ignoreip и ignoreself

Адрес из ignoreip не банится никогда. По руководству там могут быть IP-адреса, маски CIDR, DNS-имена или файл с префиксом file:. Забытый широкий диапазон, например вся офисная сеть или подсеть VPN, скрывает как раз тот трафик, которым вы тестируете. Учтите и то, что ignoreself описан как логическое значение, по умолчанию true, поэтому собственные адреса сервера не банятся никогда. Если вы тестируете, роняя входы с самой машины, ничего не произойдёт. Тестируйте с другого хоста, а список исключений смотрите так:

sudo fail2ban-client get sshd ignoreip

Часовой пояс и часы

Fail2ban сравнивает метки времени в логе с текущим временем. Окно findtime отсчитывается назад от текущего момента, поэтому строки с отметкой на часы в прошлом или будущем могут выпасть из него. Причин две: неверные системные часы и строки лога без часового пояса, которые fail2ban трактует в неверном поясе. Параметр logtimezone описан как принудительная установка часового пояса для строк лога, у которых его нет. Сравните свежую метку лога с реальным временем и используйте -o short-iso в journalctl, чтобы видеть однозначные метки:

date
sudo journalctl -u ssh -u sshd -n 5 -o short-iso

Если значения расходятся на целые часы или минуты, исправьте синхронизацию времени или задайте logtimezone и проверьте снова. О привычке регулярно читать такие логи см. мониторинг серверов и анализ логов.

Баны есть, но ничего не блокируется

Если Total banned растёт, обнаружение работает, а проблема в действии бана. Декоративный бан хуже отсутствия бана, потому что выглядит как защита. Сначала посмотрите, какое действие выполняет jail и что оно сделало:

sudo fail2ban-client get sshd actions
sudo fail2ban-client get sshd banip --with-time
sudo grep -iE "error|warning|fail" /var/log/fail2ban.log | tail -n 20

Ошибки команды действия обычно попадают в лог fail2ban. Если на вашем хосте собственный лог fail2ban уходит в другое место (проверьте logtarget в fail2ban.conf и в возможном fail2ban.local), читайте его там.

Согласуйте действие с вашим firewall

Значение banaction по умолчанию в jail.conf(5) указано как iptables-multiport, а banaction_allports как iptables-allports. Файлы путей дистрибутива могут это переопределять: upstream-файл paths-debian.conf задаёт banaction = nftables и banaction_allports = nftables[type=allports]. Поэтому проверьте, что реально использует ваш jail, и убедитесь, что соответствующее правило nftables, iptables или ufw появляется там, где вы его ищете. Наше руководство по firewall в Linux объясняет, как связаны эти три уровня, в том числе почему вывод iptables может приходить из nftables под капотом.

# подставьте адрес, который вы забанили
sudo nft list ruleset | grep 203.0.113.9
sudo iptables -S | grep 203.0.113.9
sudo ip6tables -S | grep 203.0.113.9
sudo ufw status numbered

Выполните те команды, что подходят, и ищите адрес. По upstream-файлу действия nftables, значения по умолчанию такие: таблица f2b-table в семействе inet и цепочка f2b-chain, поэтому при стандартной настройке nftables вы должны найти их в наборе правил.

Особенности ufw

Параметр banaction = ufw добавляет правила через ufw, а в файле действия ufw сказано, что сначала нужно выполнить ufw enable, иначе действие не даст эффекта. В руководстве ufw(8) указано, что порядок правил важен и срабатывает первое совпадение, а параметр add действия по умолчанию равен prepend, так что правило бана ставится перед общим разрешающим правилом. Если вы это изменили, более раннее allow для порта 22 может перебить бан. В том же файле отмечено, что ufw быстро пропускает пакеты уже существующих соединений, поэтому открытая сессия забаненного адреса может уцелеть; параметр kill-mode (ss или conntrack) закрывает такие соединения.

IPv6 и контейнеры

  • IPv6: в ufw(8) сказано, что для работы firewall по IPv6 его нужно включить в /etc/default/ufw, а в fail2ban.conf есть параметр allowipv6 со значением по умолчанию auto. Если атаки приходят по IPv6, убедитесь, что ваше действие поддерживает IPv6, и проверьте через ip6tables -S или таблицу inet в nftables.
  • Docker: в документации Docker сказано, что Docker направляет трафик контейнеров через таблицу nat, отводя пакеты до цепочек INPUT и OUTPUT, которые использует ufw. Бан на хосте не обязательно остановит трафик к опубликованному порту контейнера.

Тестовый бан от начала до конца

Единственное убедительное доказательство: бан, который вы видите в firewall и затем снимаете. Используйте адрес из документационного диапазона 203.0.113.0/24, чтобы никогда не забанить настоящий хост:

sudo fail2ban-client set sshd banip 203.0.113.9
sudo fail2ban-client get sshd banip --with-time
sudo nft list ruleset | grep 203.0.113.9
sudo iptables -S | grep 203.0.113.9
sudo tail -n 20 /var/log/fail2ban.log
sudo fail2ban-client set sshd unbanip 203.0.113.9

Руководство клиента описывает set JAIL banip и unbanip, а также get JAIL banip --with-time для списка банов. Истолкуйте результат:

  • Есть в списке fail2ban и виден в firewall: применение работает. Ваша проблема на стороне обнаружения.
  • Есть в списке fail2ban, но нет в firewall: действие бана сломано. Перечитайте раздел про применение бана и поищите ошибки действия в логе.
  • Команда завершилась ошибкой: имя jail неверное или jail не запущен.

Затем проверьте обнаружение на реальной неудаче. Со второй машины, которой нет в ignoreip, несколько раз неудачно войдите по SSH под выдуманным именем пользователя, держа админскую сессию открытой в другом терминале, и наблюдайте за счётчиками:

sudo fail2ban-client status sshd
sudo tail -f /var/log/fail2ban.log

Если при отладке нужно больше подробностей, sudo fail2ban-client set loglevel DEBUG повышает уровень детализации (названия уровней описаны в руководстве клиента, а значение по умолчанию в fail2ban.conf равно INFO). По завершении верните sudo fail2ban-client set loglevel INFO и записывайте всё постоянное в файлы конфигурации, не полагаясь на изменения во время работы. Если при тестировании вы всё же заблокировали себя, путь обратно описан в нашем руководстве по восстановлению доступа по SSH.

Баны идут, а атаки продолжаются

Иногда fail2ban исправен, а шум в логах не уменьшается. Это ожидаемо, когда атакующий меняет адреса: бан останавливает один IP, но не всю кампанию. Каждый новый адрес получает свои maxretry попыток до блокировки, поэтому ботнет из многих источников продолжает давать неудачи, как отмечено в нашем руководстве по настройке. Растущий Total banned при неизменном шуме в логах означает, что инструмент работает, но не означает, что ваш вход безопасен.

Долговременный ответ: сделать подобранные пароли бесполезными. Включите вход только по ключам, чтобы никакое число попыток не могло удаться, а fail2ban пусть занимается остальным. На странице sshd_config(5) для PasswordAuthentication и KbdInteractiveAuthentication указано значение по умолчанию yes, поэтому оба нужно явно выключить:

# /etc/ssh/sshd_config.d/10-keys-only.conf
PasswordAuthentication no
KbdInteractiveAuthentication no

Перед применением убедитесь, что вход по ключу работает из второго терминала, проверьте синтаксис командой sudo sshd -t, а затем прочитайте действующие значения через sudo sshd -T: в руководстве sshd он описан как проверка конфигурации с выводом эффективной конфигурации. Полная процедура есть в нашем руководстве по усилению защиты SSH, а общий план против этого вида атак в статье защита SSH от подбора паролей. Статьи глоссария о атаке подбором и предотвращении вторжений объясняют, какое место fail2ban занимает в этой цепочке.

Ещё два рычага помогают, когда основа уже в порядке. В поставляемом jail.conf параметр bantime.increment описан как использование базы данных для поиска ранее забаненных IP и увеличения срока их бана, а jail recidive для повторных нарушителей разобран в руководстве по настройке. И наконец, настройте оповещение о jail, который замолчал: jail, который на открытом сервере внезапно перестал фиксировать неудачи, заслуживает проверки. Эта привычка описана в нашем руководстве по анализу логов.

Часто задаваемые вопросы

Почему fail2ban работает, но никого не банит?

Обычно jail не видит неудачные входы или не может применить бан. Посмотрите fail2ban-client status для вашего jail: ноль неудач указывает на неверный лог или журнал либо несовпадение фильтра, неудачи без банов указывают на пороги или ignoreip, а баны без блокировки указывают на действие firewall.

Как проверить, что fail2ban читает правильный лог?

Выполните fail2ban-client get с именем jail и logpath, а для backend systemd journalmatch, затем откройте этот источник и убедитесь, что в нём есть свежие строки аутентификации SSH. Список отслеживаемых файлов также виден в статусе jail.

Почему fail2ban-regex показывает пропущенные строки с неудачными входами?

Это значит, что фильтр не узнаёт формат лога, поэтому jail не может учесть эти неудачи. Запустите его с --print-all-missed, сравните пропущенные строки с фильтром, проверьте режим фильтра и формат даты в строках.

Почему fail2ban пишет, что IP забанен, но он всё равно подключается?

Бан записан, но не применяется. Проверьте, что banaction соответствует вашему firewall, что правило есть в nft, iptables или ufw, что ufw включён и что уже открытое соединение просто не пережило бан. Частые пробелы также IPv6 и опубликованные порты Docker.

Почему мои собственные неудачные входы с сервера никогда не банятся?

Параметр ignoreself по умолчанию равен true, поэтому собственные адреса сервера не банятся, а любой адрес из ignoreip тоже исключён. Тестируйте с другого хоста, которого нет в ignoreip.

Нужно ли перезапускать fail2ban после правки jail.local?

Изменение применяется только после повторного чтения конфигурации. Выполните fail2ban-client -t для проверки ошибок, затем fail2ban-client reload, который перечитывает конфигурацию без перезапуска сервера, и подтвердите результат через fail2ban-client status.

Почему SSH jail показывает ноль неудач в Ubuntu или Debian?

Хост может писать sshd только в журнал, а jail читает файл, либо собственный journalmatch использует неверное имя юнита, ведь в Ubuntu служба называется ssh.service. Задайте backend systemd, проверьте journalmatch и убедитесь через fail2ban-regex на systemd-journal.

Бесполезен ли fail2ban, если атакующие постоянно меняют IP?

Нет, но он не остановит кампанию со сменой адресов, потому что каждый новый адрес получает свои попытки. Сочетайте его со входом по SSH только по ключам, чтобы подобранные пароли не работали, а fail2ban оставьте для снижения шума и отсеивания остального.

Не уверены, в каком состоянии ваш сервер?

Получите бесплатную проверку безопасности в режиме только чтения или поговорите с инженером по безопасности об управляемой защите.

Получить бесплатную проверку сервера Связаться с инженером по безопасности