Про fail2ban написано уже много, в том числе и на хабре. Эта статья немного о другом — как сделать защиту им еще надежнее и о еще пока неизвестных в широких кругах новых функциях fail2ban. Добавлю сразу — речь пойдет пока про development branch, хотя уже долго проверенный в бою.
Краткое вступление
В большинстве своем fail2ban устанавливается из дистрибутива (как правило это какая-нибудь
Так вот, сподвигнуть на написание этого поста меня заставил случай, произошедший с одним сервером моего хорошего знакомого. Классика жанра — пришла абуза, за ней вторая и пошло поехало. Хорошо еще злоумышленник попался ленивый — логи не потер, да и повезло еще крупно, что logrotate был настроен, чтобы хранить логи месяцами.
Оказалось все довольно банально, подобрали пароль к его админской почте, который по совместительству был паролем для ssh (естественно без ключа). Не рут, но судоер, со всеми вытекающими. Первый его вопрос был: как подобрали — у меня же fail2ban там. И вот здесь как раз засада: не все представляют себе, что подбором паролей сегодня занимаются уже не отдельные компьютеры, а целые бот-сети, кстати поумневшие донельзя. Так вот по логам выяснили, что тут как раз такой случай: перебирала бот-сеть, причем на практике выяснившая его настройки в fail2ban (maxRetry=5, findTime=600 и banTime=600). Т.е. чтобы избежать бана, сеть делала 4 попытки в течении 10 минут с каждого IP. На минуточку в сети порядка 10 тысяч уникальных IP = что-то более 5 с половиной миллионов паролей в сутки.
Кроме того его почтовик делал большую глупость — а именно паузу до 10 секунд, при логине с неправильным именем. Т.е. выяснить, что некоторые имена, в том числе admin, реально имеются, этой сетке не составило труда. Далее шел целенаправленный перебор только паролей для имеющихся имен.
Подробнее на «ремонте» останавливаться не буду — это история долгая, и вообще тема для отдельной статьи. Скажу только, что все почистили и все разрешилось малой кровью, да и отделался он практически «легким» испугом.
Так вот, мысль написать статью возникла после того, как мне (частично заслужено) было высказано: «Так ты про такое знал и ничего не сказал, не предупредил. Да еще и решение есть и не поделился. Ну и сволочь ты». Короче, посему посту — быть.
Мой fail2ban
К безопасности «своих» серверов я отношусь чрезвычайно серьезно. Кроме того же fail2ban, всегда кастомного донельзя, у меня там и мониторинг и еще куча всего. Меня просто реально бесит, что из-за бестолковой серой массы, позволяющей брать под контроль бот-сетей свое железо, приходится убивать уйму времени на защиту (и постоянное сопровождение и контроль ее в дальнейшем). Кстати, чтобы минимизировать этот контроль, я и участвую активно в разработке и fail2ban, да и других проектов от безопасности.
Так вот, моя последняя расширенная версия [sebres:ban-time-incr], позволяет вывести этот назойливый зоопарк раз и навсегда (ну или пока они снова не приспособятся). Это фишка довольно часто обсуждалась всем коммюнити, но как-то руки не доходили. У меня оно жило в виде отдельных скриптов и каких-то кастомных изменений, пока не оформилось в готовый функционал.
Если коротко, то система, запоминая плохие IP адреса, позволяет каждый раз динамически (экспоненциально) увеличивать время блокировки (banTime) в зависимости от количества предыдущих запретов (banCount). При этом также каждый раз уменьшая количество (maxRetry) возможных провальных попыток (failure) до следующего бана. Наглядно это можно увидеть на следующем примере:
[Click to view LOG]
2014-09-23 20:05:31,146 fail2ban.observer [named-refused] Increase Ban XXX.XXX.XX.XXX (10 # 5 days, 8:04:55 -> 2014-09-29 04:10:24)
2014-09-23 20:05:31,120 fail2ban.actions [named-refused] Ban XXX.XXX.XX.XXX (_ # 0:15:00 -> 2014-09-23 20:20:29)
2014-09-23 15:30:32,625 fail2ban.actions [named-refused] Unban XXX.XXX.XX.XXX
2014-09-20 23:24:14,620 fail2ban.observer [named-refused] Increase Ban XXX.XXX.XX.XXX (9 # 2 days, 16:06:18 -> 2014-09-23 15:30:31)
2014-09-20 23:24:14,569 fail2ban.actions [named-refused] Ban XXX.XXX.XX.XXX (_ # 0:15:00 -> 2014-09-20 23:39:13)
2014-09-20 21:10:36,708 fail2ban.actions [named-refused] Unban XXX.XXX.XX.XXX
2014-09-19 13:03:03,377 fail2ban.observer [named-refused] Increase Ban XXX.XXX.XX.XXX (8 # 1 day, 8:07:34 -> 2014-09-20 21:10:36)
2014-09-19 13:03:03,361 fail2ban.actions [named-refused] Ban XXX.XXX.XX.XXX (_ # 0:15:00 -> 2014-09-19 13:18:02)
2014-09-19 12:38:17,743 fail2ban.actions [named-refused] Unban XXX.XXX.XX.XXX
2014-09-18 20:13:23,647 fail2ban.observer [named-refused] Increase Ban XXX.XXX.XX.XXX (7 # 16:24:55 -> 2014-09-19 12:38:17)
2014-09-18 20:13:23,620 fail2ban.actions [named-refused] Ban XXX.XXX.XX.XXX (_ # 0:15:00 -> 2014-09-18 20:28:22)
2014-09-18 20:07:06,053 fail2ban.actions [named-refused] Unban XXX.XXX.XX.XXX
2014-09-18 12:03:53,282 fail2ban.observer [named-refused] Increase Ban XXX.XXX.XX.XXX (6 # 8:03:14 -> 2014-09-18 20:07:05)
2014-09-18 12:03:53,266 fail2ban.actions [named-refused] Ban XXX.XXX.XX.XXX (_ # 0:15:00 -> 2014-09-18 12:18:51)
2014-09-18 11:22:40,704 fail2ban.actions [named-refused] Unban XXX.XXX.XX.XXX
2014-09-18 07:11:12,200 fail2ban.observer [named-refused] Increase Ban XXX.XXX.XX.XXX (5 # 4:09:43 -> 2014-09-18 11:20:54)
2014-09-18 07:11:12,160 fail2ban.actions [named-refused] Ban XXX.XXX.XX.XXX (_ # 0:15:00 -> 2014-09-18 07:26:11)
2014-09-18 06:47:46,618 fail2ban.actions [named-refused] Unban XXX.XXX.XX.XXX
2014-09-18 04:37:29,972 fail2ban.observer [named-refused] Increase Ban XXX.XXX.XX.XXX (4 # 2:02:16 -> 2014-09-18 06:39:44)
2014-09-18 04:37:29,967 fail2ban.actions [named-refused] Ban XXX.XXX.XX.XXX (_ # 0:15:00 -> 2014-09-18 04:52:28)
2014-09-18 04:32:49,491 fail2ban.actions [named-refused] Unban XXX.XXX.XX.XXX
2014-09-18 02:55:05,706 fail2ban.observer [named-refused] Increase Ban XXX.XXX.XX.XXX (3 # 1:23:31 -> 2014-09-18 04:18:35)
2014-09-18 02:55:05,698 fail2ban.actions [named-refused] Ban XXX.XXX.XX.XXX (_ # 0:15:00 -> 2014-09-18 03:10:04)
2014-09-18 01:18:37,976 fail2ban.actions [named-refused] Unban XXX.XXX.XX.XXX
2014-09-18 00:40:09,592 fail2ban.observer [named-refused] Increase Ban XXX.XXX.XX.XXX (2 # 0:38:30 -> 2014-09-18 01:18:37)
2014-09-18 00:40:09,548 fail2ban.actions [named-refused] Ban XXX.XXX.XX.XXX (_ # 0:15:00 -> 2014-09-18 00:55:07)
2014-09-17 22:47:05,872 fail2ban.actions [named-refused] Unban XXX.XXX.XX.XXX
2014-09-17 22:32:05,804 fail2ban.actions [named-refused] Ban XXX.XXX.XX.XXX (_ # 0:15:00 -> 2014-09-17 22:47:05)
Здесь хорошо заметно, как каждый следующий бан продлевает время блокировки от 15 минут (0:15:00) первый раз, до 5 с лишним дней (5 days, 8:04:55) после десятой блокировки. У меня в базе есть IP у которых «срок» уже — от нескольких месяцев до перманентного бана.
Ниже можно увидеть, как новый функционал отразился на решении собственно банить IP XXX.XXX.XX.XXX. В примере параметр maxRetry установлен равным 5. Так мы видим, что пока IP не признан плохим он был первый раз забанен после 5-ти попыток, второй раз, уже как плохой — после 3-х (каждая попытка была засчитана за 2), третий и т.д. — после 2-х (попытка идет за 3) и четвертый раз забанен сразу после первой попытки (считается сразу за 5-ть):
[Click to view LOG]
2014-09-18 04:37:29,155 fail2ban.observer [named-refused] Found XXX.XXX.XX.XXX, bad - 2014-09-18 04:37:28, 3 # -> 5, Ban
2014-09-18 04:37:29,148 fail2ban.filter [named-refused] Found XXX.XXX.XX.XXX - 2014-09-18 04:37:28
......
2014-09-18 02:55:04,790 fail2ban.observer [named-refused] Found XXX.XXX.XX.XXX, bad - 2014-09-18 02:55:04, 2 # -> 3, Ban
2014-09-18 02:55:04,763 fail2ban.filter [named-refused] Found XXX.XXX.XX.XXX - 2014-09-18 02:55:04
2014-09-18 02:22:37,683 fail2ban.observer [named-refused] Found XXX.XXX.XX.XXX, bad - 2014-09-18 02:22:37, 2 # -> 3
2014-09-18 02:22:37,648 fail2ban.filter [named-refused] Found XXX.XXX.XX.XXX - 2014-09-18 02:22:37
......
2014-09-18 00:40:08,908 fail2ban.observer [named-refused] Found XXX.XXX.XX.XXX, bad - 2014-09-18 00:40:08, 1 # -> 2, Ban
2014-09-18 00:40:08,625 fail2ban.filter [named-refused] Found XXX.XXX.XX.XXX - 2014-09-18 00:40:08
2014-09-17 23:48:54,404 fail2ban.observer [named-refused] Found XXX.XXX.XX.XXX, bad - 2014-09-17 23:48:53, 1 # -> 2
2014-09-17 23:48:54,397 fail2ban.filter [named-refused] Found XXX.XXX.XX.XXX - 2014-09-17 23:48:53
2014-09-17 22:49:04,647 fail2ban.observer [named-refused] Found XXX.XXX.XX.XXX, bad - 2014-09-17 22:49:03, 1 # -> 2
2014-09-17 22:49:04,620 fail2ban.filter [named-refused] Found XXX.XXX.XX.XXX - 2014-09-17 22:49:03
......
2014-09-17 22:32:05,593 fail2ban.filter [named-refused] Found XXX.XXX.XX.XXX - 2014-09-17 22:32:05
2014-09-17 22:06:29,952 fail2ban.filter [named-refused] Found XXX.XXX.XX.XXX - 2014-09-17 22:06:29
2014-09-17 21:47:43,439 fail2ban.filter [named-refused] Found XXX.XXX.XX.XXX - 2014-09-17 21:47:42
2014-09-17 20:43:41,490 fail2ban.filter [named-refused] Found XXX.XXX.XX.XXX - 2014-09-17 20:43:40
2014-09-17 16:44:35,130 fail2ban.filter [named-refused] Found XXX.XXX.XX.XXX - 2014-09-17 16:44:34
Без этой логики подсчета failure, умные бот-сети научились подстраивать свою работу так, чтобы просто не попадать в бан. Когда я допилил таки и эту логику и выкатил в продакшн, за считанные дни я избавился практически от всей той нечисти, которую привык видеть годами в своих логах. Например, сейчас средний нормальный ежедневный прирост моих auth.log где то в районе 20-50 строк, раньше на некоторых серверах он был в сотни и тысячи раз больше.
Пока что это development branch, лежит pull request-ом, релиз запланирован пока в версии 0.9.2.
Кому интересно, почитать подробнее про реализацию и историю решения можно здесь — Ban time incr by sebres · Pull Request #716 · fail2ban/fail2ban.
Однако пока эта версия ляжет апдейтом на ваш сервер, пройдет еще немало времени — пока релиз выйдет в mainline, пока его в дистрибутивы возьмут… История длинная, например тот же debian все еще использует 0.8.x — собственно поэтому и статья. Так что качаем руками, устанавливаем… профит.
Взять эту версию можно здесь fail2ban-ban-time-incr.zip (sebres master branch).
Порт для debian-ов: ban-time-incr-debian.zip (merged master debian branch, и хоть и не main line — буду стараться по возможности поддерживать ветку актуальной).
Установить его довольно просто. Если у вас уже до того был fail2ban, установленный из дистрибутива, сохраняем из "/etc/fail2ban/" старые «fail2ban.local» и «jail.local» (Ну и лучше старые «fail2ban.conf» и «jail.conf»). Я бы на всякий случай (из-за возможных личных изменениях в filter и action) сохранил бы куда-нибудь весь каталог "/etc/fail2ban/".
Далее сносим старый дистрибутивный fail2ban, например:
sudo service fail2ban stop
sudo apt-get remove fail2ban
Собственно установка:
cd /tmp
unzip ~/downloads/fail2ban-ban-time-incr.zip
cd fail2ban-ban-time-incr/
sudo python setup.py install
[UPD] Иногда на некоторых дистрибутивах, при ручной установке, почему-то не устанавливается сервис (например нет файла
/etc/init.d/fail2ban
) — и соответственно через сервис не (авто)стартует, только через fail2ban-client start
.[Что делать...]
[/UPD]Это можно поправить копированием из дистрибутива или например из архива (потом не забываем поправить права):
— при
— при
Не забываем проверить автозапуск сервиса (update-rc.d, rcconf, file-rc… любимое подставить).
cd /tmp/fail2ban-ban-time-incr-debian
sudo cp /etc/init.d/fail2ban ~/init.fail2ban.org
sudo cp ./files/debian-initd /etc/init.d/fail2ban
chmod u+x,g+x,o+x /etc/init.d/fail2ban
И проверить внутри путь к fail2ban-client (which fail2ban-client
):— при
/usr/local/bin/fail2ban-client
— DAEMON=/usr/local/bin/$NAME-client
— при
/usr/bin/fail2ban-client
— DAEMON=/usr/bin/$NAME-client
Не забываем проверить автозапуск сервиса (update-rc.d, rcconf, file-rc… любимое подставить).
Теперь чтобы заработал новый функционал, нужно в вашем jail.local в [default] (либо для каждой конкретной jail) добавить опцию:
bantime.increment = true
. Пример и описание можно пока найти в "jail.conf". Некоторое здесь кратко:
bantime.rndtime
— максимальное время, используется для добавления кbanTime
случайного времени, для предотвращения «умных» бот-сетей вычислять точное время, когда IP разблокируется снова. Примерbantime.rndtime = 10m
bantime.factor
— коэффициент для вычисления экспоненты роста для формулыbantime.formula
или множителейbantime.multipliers
, по умолчанию значение коэффициента 1, что соответствует увеличению времени запрета на 1, 2, 4, 8, 16… Увеличивая этот параметр для некоторых jail, можно увеличивать время блокировки более агрессивно.bantime.formula
— используется по умолчанию для вычисления следующего значения времени запрета, значение по умолчанию:bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
Тот же рост времени запрета будет достигнут используя множителиbantime.multipliers
равные 1, 2, 4, 8, 16, 32…
Пример более агрессивной формулы для фактора «1» и имеет те же значения роста только для фактора равного «2.0 / 2.885385":
bantime.formula = ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)
bantime.multipliers
— параметр может используется вместо формулы дискретно для вычисления следующего значения времени запрета. Значение множителя равное "-1" (может стоять только в конце списка) и заносит адрес в перманентный бан (до ручного разблокирования).
Пример 1:bantime.multipliers = 1 2 4 8 16 32 64
— увеличивает время запрета на 1, 2, 4,… и если последний ban count был больше последнего индекса мультипликаторов, то будет всегда использован последний множитель (64 в примере), что при факторе равном «1» и оригинальном времени запрета (10 минут) — соответствует 10.6 часам.
Пример 2:bantime.multipliers = 1 5 30 60 300 720 1440 2880
— может использоваться для небольшого начального времени запрета (bantime = 60) — т.к. увеличение становится более агрессивным, имеем bantime равный: 1 мин, 5 мин, 30 мин, 1 час, 5 часов, 12 часов, 1 день, 2 дня соответственно.
Если во время проб или в продакшн какой-нибудь (хороший) IP случайно многократно улетел в бан (и стал соответственно плохим) он забудется (снова станет «белым») сам по истечении трехкратного времени последнего бана и dbpurgeage (находится в fail2ban.local), либо если с него руками снять бан, используя:
fail2ban-client set $JAIL unbanip $IP
Я для тестирования регулярок использую fail2ban-regex, а для теста работоспособности что-нибудь типа:
logger -t 'test:auth' -i -p auth.info "pam_unix(test:auth): authentication failure; logname= uid=0 euid=0 tty=test ruser=admin rhost=1.2.3.4"
Не забываем про старт:
sudo service fail2ban start
Вот собственно и все, теперь надеюсь ваш сервер стал еще чуточку защищенней. Ну а вы не ленитесь и поглядывайте все-таки в логи (доверяй, но проверяй).
P.S. Стандартная приписка: Fail2Ban is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. Короче говоря — пользуйтесь на здоровье, но на свой страх и риск…
И да пребудут ваши сервера в безопасности.
P.P.S. Да чуть не забыл, у меня тут назапланировано что-то допилить, что-то уже готово, нужно просто оформить нормально и выложить, так вот — опрос «Что по вашему следовало бы (до)делать в первую очередь».
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Что по вашему следовало бы (до)делать в первую очередь
69.09% веб-морда для управления fail2ban, статистики, unban и т.д.552
38.92% функция «forget by success»: удалять IP из таблицы «плохие ip» при успешном логине, если при этом banCount был невелик, иначе все-таки адрес совсем плохой — «alarm by success» — письмо админу311
39.05% автоматическая генерация абуз (ну должен же кто-то делать интернет чище)312
2.63% есть другая (хорошая) идея, напишу в комментариях21
Проголосовали 799 пользователей. Воздержались 200 пользователей.