Fail2ban [incremental]: Краще, швидше, надійніше

fail2ban image
Про fail2ban написано вже багато, в тому числі і на хабре. Ця стаття трохи про інше — як зробити захист їм ще надійніше і про ще поки невідомих в широких колах нових функціях fail2ban. Додам відразу — йдеться поки про development branch, хоча вже довго перевірений в бою.

Короткий вступ

В більшості своїй fail2ban встановлюється з дистрибутива (як правило це якась стабільна стара версія) і налаштовується за манам з інтернету за кілька хвилин. Потім роками працює, без втручання адміна. Нерідко навіть логи, за якими начебто стежить 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) до наступного бана. Наочно це можна побачити на наступному прикладі:
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-ть):
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.

Однак поки ця версія ляже апдейтом на ваш сервер, пройде ще чимало часу — поки реліз вийде магістралі, поки його в дистрибутиви візьмуть… Історія довга, наприклад той же debian все ще використовує 0.8.x — власне тому і стаття. Так що качаємо руками, встановлюємо… профіт.

Взяти цю версію можна тут fail2ban-ban-time-incr.zip

Встановити його досить просто. Якщо у вас вже до того був 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

Тепер щоб запрацював новий функціонал, потрібно у вашому 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. Так мало не забув, у мене тут назапланировано щось допилити, що вже готове, потрібно просто оформити нормально і викласти, так от — опитування «Що на вашу слід було б (до)робити в першу чергу».

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.