Fail2ban для Zimbra: 44 тысячи попыток входа за две недели

Дистрибьютор в Хамовниках, 40 рабочих мест. В августе 2022-го у них каждый день по трое-четверо не могли попасть в веб-почту, а к вечеру сервер начинало заметно вести. Разбираю, почему встроенный фильтр Zimbra создаёт больше жалоб, чем закрывает, как собрать fail2ban на три джейла и как я в процессе отрезал от почты весь офис.

«Почта меня не пускает», и так каждый день

Август 2022-го, дистрибьютор строительных материалов в Хамовниках. Сорок рабочих мест, пятьдесят восемь ящиков, менеджеры сидят в веб-почте целыми днями. Заявка была сформулирована так: «каждый день по три-четыре человека не могут зайти, через час всё само проходит».

Классика этого жанра — админ разводит руками. Сервер работает. Почта ходит. У остальных тридцати шести всё открывается. Человек называет свой пароль вслух, вводится он правильно, и всё равно — «Network Error» на белом экране. Через час заходит.

Если у вас так же и вы списываете это на «интернет барахлит» — почти наверняка вы уже полгода живёте под перебором паролей и просто об этом не знаете. Сервер защищается. Он делает ровно то, что должен. Просто побочный эффект этой защиты выглядит как поломка.

Второй симптом там был тоже: к вечеру веб-почта заметно тяжелела. Не падала — именно тяжелела, письмо открывалось не за полсекунды, а за три-четыре.

Что было на входе

Сервер: Zimbra 8.8.15 Patch 30, CentOS 7.9, четыре ядра, шестнадцать гигабайт памяти. Наружу торчали 25, 465, 587, 993, 995, 443 и — отдельным пунктом — 7071, консоль администратора.

su - zimbra
zmcontrol -v
# Release 8.8.15.GA.3869.RHEL7_64 FOSS edition Patch 8.8.15_P30

ss -lntp | grep -E ':(25|443|465|587|993|995|7071)\b'
uptime
# 18:41:02 up 212 days, load average: 5.84, 5.11, 4.02

Load average почти шесть при четырёх ядрах — вечером. Утром была единица. Это уже само по себе странно: людей утром и вечером примерно поровну.

Версия про прокси, которую я честно отработал

«Network Error» в веб-почте Zimbra — известный симптом проблем с прокси и memcached. Когда сервлет не может достучаться до memcached, интерфейс отдаёт ровно такую ошибку. Я туда и полез, потому что версия хорошая: объясняет и случайность попадания, и то, что через час проходит само.

grep -i "memcache" /opt/zimbra/log/mailbox.log | tail -20
echo stats | nc 127.0.0.1 11211 | head -12
zmproxyctl status
tail -50 /opt/zimbra/log/nginx.log

Memcached живой, статистика ровная, вытеснений нет. В nginx.log ничего похожего на разрывы. Я даже перезапустил memcached и прокси на пробу — на следующий день двое опять не зашли.

Версия не подтвердилась, и мне это тогда испортило вечер. Зато научило смотреть в правильный лог: жалоба «не пускает в почту» разбирается не в прокси, а в журнале аутентификации.

grep "Access to IP suspended" /opt/zimbra/log/mailbox.log | tail -5
# ...  [oip=91.204.x.x] Access to IP suspended, for repeated failed login
# ...  [oip=91.204.x.x] Access to IP suspended, for repeated failed login

Вот и ответ. Сработал встроенный фильтр Zimbra, который блокирует адрес после серии неудачных попыток входа. А адрес 91.204.x.x — это внешний адрес офиса, за которым сидели все сорок человек.

44 216 попыток за четырнадцать дней

Дальше я пошёл в журнал аудита. В Zimbra он отдельный и пишет по каждой попытке входа строку с адресом источника, аккаунтом и результатом.

grep "authentication failed" /opt/zimbra/log/audit.log* | wc -l
# 44216

grep "authentication failed" /opt/zimbra/log/audit.log* \
  | grep -oP 'oip=\K[0-9.]+' | sort | uniq -c | sort -rn | head -8
#   9843 45.155.x.x
#   6104 194.26.x.x
#   5390 141.98.x.x
#   3277 80.66.x.x
#   ...
#     61 91.204.x.x     <-- офис

grep "authentication failed" /opt/zimbra/log/audit.log* \
  | grep -oP 'oip=\K[0-9.]+' | sort -u | wc -l
# 312

Сорок четыре тысячи двести шестнадцать неудачных попыток за две недели с 312 разных адресов. Один адрес дал почти десять тысяч в одиночку. Аккаунты подбирали по словарю: admin, info, sales, test, zimbra, потом фамилии из корпоративных подписей на сайте компании.

Вот отсюда и вечерняя нагрузка. Каждая попытка входа — это обращение к LDAP, вычисление хэша и запись в журнал. Само по себе дёшево. Сорок четыре тысячи раз — уже нет.

И отсюда же — жалобы. Офис сидел за одним внешним адресом. Кто-то один ошибался паролем несколько раз подряд, счётчик по адресу набегал, встроенный фильтр вешал блокировку на весь адрес — и следующие полчаса не мог зайти вообще никто, кто в этот момент попытался.

Встроенный фильтр: что он делает и чего не делает

В Zimbra есть свой механизм от перебора, он включён по умолчанию, и о нём мало кто знает, пока не наткнётся.

zmprov gacf | grep -iE "InvalidLoginFilter|DosFilter|ThrottleSafeIPs"
# zimbraInvalidLoginFilterMaxFailedLogin: 10
# zimbraInvalidLoginFilterDelayInMinBetwnReqBeforeReinstating: 15
# zimbraInvalidLoginFilterReinstateIpTaskIntervalInMin: 5
# zimbraHttpDosFilterMaxRequestsPerSec: 30
# zimbraHttpDosFilterDelayMillis: -1
# zimbraHttpThrottleSafeIPs:

Читается это так: после десяти неудачных попыток адрес отправляется в бан, снимается он не раньше чем через пятнадцать минут, задача разблокировки крутится раз в пять минут. Отдельно живёт фильтр по частоте запросов — тридцать в секунду.

Что он делает хорошо: спасает сервер от совсем уж дурного флуда, работает из коробки, ничего не требует.

Чего он не делает совсем:

  • Не различает своих и чужих. Ваш офисный NAT для него такой же адрес, как хостинг в Нидерландах. Пока вы не заполните zimbraHttpThrottleSafeIPs, свои будут ловить бан наравне со всеми.
  • Не трогает SMTP. Перебор по 465 и 587 идёт мимо него полностью — там работает Postfix со своим SASL, у которого свой лог и никакой связи с этим фильтром.
  • Не режет трафик на входе. Соединение всё равно устанавливается, обработка всё равно происходит, отказ выдаёт приложение. Нагрузка на сервер остаётся.
  • Забывает быстро. Пятнадцать минут — и адрес снова может пробовать. Для ботнета из трёхсот адресов это не препятствие вообще.

Первым делом мы прописали офис в белый список и заодно подняли порог самого фильтра — вдвоём это сняло жалобы в тот же день, ещё до всякого fail2ban:

zmprov mcf zimbraHttpThrottleSafeIPs 91.204.x.x
zmprov mcf +zimbraHttpThrottleSafeIPs 10.7.0.0/24
# порог в 10 попыток на весь офис за одним NAT — это мало
zmprov mcf zimbraInvalidLoginFilterMaxFailedLogin 40
zmmailboxdctl restart

Обратите внимание на первую строку. В ней я собственными руками написал то, что через неделю забуду применить к fail2ban: для сервера в дата-центре весь офис — это один внешний адрес 91.204.x.x, а не подсеть 10.7.0.0/24. Запомните эту деталь, она ещё выстрелит.

Проверьте у себя за 2 минуты

Три команды, читают только логи и конфиг.

su - zimbra
grep -c "authentication failed" /opt/zimbra/log/audit.log
grep "Access to IP suspended" /opt/zimbra/log/mailbox.log | wc -l
zmprov gacf zimbraHttpThrottleSafeIPs
Что получилосьЧто это значит
Первая цифра — сотни за суткиФоновый шум интернета. Нормально, живут все
Первая цифра — тысячи за суткиВас целенаправленно перебирают. Пора закрываться
Первая цифра — десятки тысячПеребор уже влияет на производительность. Ищите вечерний провал по отзывчивости
Вторая цифра больше нуляВстроенный фильтр срабатывал. Проверьте, чей адрес он банил — часто ваш собственный
Третья команда вернула пустое значениеБелого списка нет. Ваш офис банится наравне с ботами, отсюда и жалобы «не пускает»

Отдельно посмотрите перебор по SMTP — он идёт совсем другим маршрутом и в аудит Zimbra не попадает:

grep -c "SASL LOGIN authentication failed" /var/log/zimbra.log

Fail2ban: три джейла, а не один

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

Первый фильтр, веб-почта и SOAP — /etc/fail2ban/filter.d/zimbra-webmail.conf:

[Definition]
failregex = ^.*\[oip=;.*\] security - cmd=Auth; account=.*; protocol=soap; error=authentication failed for .*$
            ^.*\[oip=;.*\] SoapEngine - handler exception: authentication failed for .*$
ignoreregex =

Второй, консоль администратора на 7071 — zimbra-admin.conf. Отличается командой в строке журнала, и это принципиально: попытки войти в админку надо банить жёстче и на дольше, чем ошибки обычных пользователей.

[Definition]
failregex = ^.*\[oip=;.*\] security - cmd=AdminAuth; account=.*; error=authentication failed for .*$
ignoreregex =

Третий, SMTP — zimbra-smtp.conf, читает транспортный лог:

[Definition]
failregex = ^.*postfix.*: warning: [-._\w]+\[\]: SASL (LOGIN|PLAIN) authentication failed.*$
ignoreregex =

И сами джейлы в /etc/fail2ban/jail.d/zimbra.conf. Привожу их ровно в том виде, в каком я запустил их в первый раз, — со строкой ignoreip, которая через двенадцать минут стоила мне очень неприятного телефонного разговора. Чем именно она кончилась, расскажу в следующем разделе.

[DEFAULT]
ignoreip = 127.0.0.1/8 10.7.0.0/24
banaction = iptables-multiport
backend = auto

[zimbra-webmail]
enabled  = true
filter   = zimbra-webmail
logpath  = /opt/zimbra/log/audit.log
port     = 80,443
maxretry = 8
findtime = 600
bantime  = 3600

[zimbra-admin]
enabled  = true
filter   = zimbra-admin
logpath  = /opt/zimbra/log/audit.log
port     = 7071
maxretry = 3
findtime = 600
bantime  = 604800

[zimbra-smtp]
enabled  = true
filter   = zimbra-smtp
logpath  = /var/log/zimbra.log
port     = 25,465,587
maxretry = 6
findtime = 600
bantime  = 86400

Проверка регекспа до боевого запуска — обязательный шаг, иначе получите джейл, который молча ничего не ловит:

fail2ban-regex /opt/zimbra/log/audit.log /etc/fail2ban/filter.d/zimbra-webmail.conf
# Lines: 199224 lines, 0 ignored, 44216 matched, 155008 missed
systemctl restart fail2ban
fail2ban-client status zimbra-webmail

Отдельная заметка для тех, кто уже переехал на Carbonio: там fail2ban по умолчанию логи не видит (ловил это на Ubuntu 24.04, где backend по умолчанию inotify), ему нужно явно задать backend = polling в джейле. Симптом ровно тот же, что при кривом регекспе — джейл запущен, банов ноль.

Как я отрезал от почты весь офис

Первый запуск я сделал в среду днём. Через двенадцать минут телефон разорвался: почта не открывается ни у кого.

Самое обидное, что двумя днями раньше я собственными руками вписал внешний адрес офиса в zimbraHttpThrottleSafeIPs — то есть прекрасно знал, каким адресом этот офис виден серверу. А джейлы принёс шаблоном от другого клиента, где Zimbra стояла в офисной серверной и белым списком честно была внутренняя подсеть. Скопировал файл, поправил пути к логам и пороги, а строку ignoreip перечитать не удосужился: она же «уже настроена».

В итоге в ignoreip у меня осталась внутренняя сеть — 10.7.0.0/24. Только сервер видит не её. Сервер стоял в дата-центре, офис ходил на него через интернет, и с точки зрения сервера все сорок человек приходили с одного внешнего адреса. Внутренняя подсеть в ignoreip не значила ровным счётом ничего: ни один пакет с таким адресом источника до сервера не доходил в принципе.

Дальше арифметика. maxretry = 8, окно десять минут. Сорок человек за одним адресом. Достаточно, чтобы за десять минут восемь раз кто-нибудь ошибся паролем — а при сорока людях это происходит регулярно, особенно в понедельник и после отпусков. Адрес офиса улетел в бан на час.

fail2ban-client status zimbra-webmail
# Currently banned: 27
# Banned IP list: ... 91.204.x.x ...

fail2ban-client set zimbra-webmail unbanip 91.204.x.x
iptables -L f2b-zimbra-webmail -n --line-numbers | head

Лечится это одной строкой, но найти её надо до запуска, а не после:

[DEFAULT]
# 91.204.x.x — тот адрес, которым офис виден СЕРВЕРУ, а не адрес его LAN
ignoreip = 127.0.0.1/8 91.204.x.x 10.7.0.0/24

[zimbra-webmail]
maxretry = 20

Простой составил двадцать пять минут для сорока человек. Обидно и на ровном месте.

Выводы, которые я с тех пор применяю всегда:

  • В ignoreip идёт тот адрес, который видит сервер, а не тот, который вы считаете офисным. Проверяется одной командой с рабочей станции: curl -s ifconfig.me. Оговорка: если офис ходит к серверу через VPN до дата-центра, а в интернет — напрямую, ответ будет другим; тогда смотрите oip= в audit.log для заведомо своего входа.
  • Если у клиента динамический внешний адрес — прописывать надо всю подсеть провайдера или ставить постоянный. Иначе однажды это выстрелит ночью.
  • Для офиса за NAT maxretry = 8 мало. Мы подняли до 20 в окне десять минут: для сорока человек это нормально, а для бота, который делает тысячи попыток, разница между восемью и двадцатью не значит ничего.
  • Мобильные клиенты с устаревшим сохранённым паролем — отдельный источник ложных банов. Телефон ретраит IMAP молча, человек об этом даже не знает.

Последний пункт я тоже поймал на своей шкуре: домашний адрес коммерческого директора улетел в бан, потому что у него в планшете лежал старый пароль и планшет стучался каждые пять минут двое суток подряд.

Что получилось и что это стоило

Цифры через месяц после настройки.

  • За первые сутки fail2ban выдал 41 бан. За месяц — 1 174.
  • Неудачных попыток в журнале аудита: было 44 216 за две недели, стало 2 890 за месяц. Падение примерно в пятнадцать раз.
  • Вечерний load average опустился с 5,8 до 1,4.
  • Жалоб «не пускает в почту» за месяц — ноль. Не считая тех двадцати пяти минут, которые устроил лично я.

Работа заняла 7 часов вместе с разбором и правкой после моего же промаха. Клиенту это обошлось в 26 000 рублей.

Теперь про цену бездействия. Прямых убытков перебор не приносит до тех пор, пока не подберёт пароль. Но косвенные считаются легко: три-четыре человека в день по часу не могут работать с почтой — это порядка 60 человеко-часов в месяц у компании на сорок мест. Считаю в деньгах: менеджер по продажам с окладом на руки около 75 000 рублей обходится работодателю примерно в 700 рублей за рабочий час со всеми налогами и взносами, значит 60 часов — это 42 000 рублей в месяц выброшенного ФОТ. То есть перебор паролей съедал у них полторы моих настройки каждый месяц. Плюс сервер, который вечером тормозит, и админ, который каждую неделю разбирает одну и ту же заявку, — этих часов я в 42 000 даже не считал.

А потом в один день перебор всё-таки попадает. У них в списке подбираемых аккаунтов был admin, и консоль администратора торчала наружу на 7071. Пароль там был приличный, повезло. Но ставка в этой игре — не «час без почты», а весь сервер целиком.

Что здесь на самом деле сложно

Регекспы и конфиги я привёл целиком, они рабочие. Сложность не в них.

Сложно понять, какой лог за что отвечает: неудачный вход в веб-почту, в админку и по SMTP пишутся в разные файлы разными форматами, и джейл, собранный по одному примеру из интернета, ловит треть событий. Сложно подобрать пороги под конкретный офис за NAT, не превратив защиту в источник заявок. Сложно не забыть про белый список до первого запуска, а не после. И совсем отдельная история — понять, чем перебор паролей отличается от уже случившегося взлома: если в журнале есть успешный вход с адреса, который до этого перебирал, fail2ban вам уже не поможет.

Проще всего начать с малого. Выполните три команды из блока самодиагностики и пришлите мне вывод — за день скажу, перебирают ли вас, попадает ли ваш офис под собственную защиту сервера и что настраивать в первую очередь.

Частые вопросы

Зачем fail2ban, если в Zimbra уже есть свой фильтр по IP?

Встроенный фильтр решает узкую задачу: не дать одному адресу забить сервер попытками входа. Он не различает свой офис и чужой хостинг, не трогает перебор по SMTP вообще, отпускает адрес через пятнадцать минут и не мешает установке соединения — отказ выдаёт уже приложение, то есть нагрузка на сервер остаётся. Fail2ban режет на уровне iptables, до того как пакет дойдёт до Zimbra, банит на сутки и на неделю, а не на четверть часа, и умеет разные правила для веб-почты, админки и SMTP. Они не заменяют друг друга, а работают вместе.

Почему у нас блокируется собственный офис?

Потому что для сервера весь ваш офис — это один внешний IP-адрес. Кто-то один ошибся паролем несколько раз подряд, счётчик добежал до порога, и адрес улетел в бан целиком со всеми сорока сотрудниками. Лечится двумя вещами. Первая: внести реальный внешний адрес офиса в zimbraHttpThrottleSafeIPs на стороне Zimbra и в ignoreip на стороне fail2ban. Вторая: поднять порог maxretry — для офиса на сорок человек восемь попыток за десять минут мало, мы ставили двадцать. На эффективность против ботов это не влияет никак.

Как понять, что перебор паролей уже увенчался успехом?

Ищите в журнале аудита успешный вход с адреса, который до этого фигурировал среди перебирающих. Берёте топ IP по неудачным попыткам и по каждому смотрите, нет ли рядом строки с успешной аутентификацией. Если есть — fail2ban уже не поможет, это разбор инцидента: смена паролей, инвалидация сессий, проверка правил пересылки в ящиках и поиск следов на сервере. Отдельно проверьте, не появилось ли у ящиков автоматической пересылки на посторонний адрес — это первое, что настраивает злоумышленник после успешного входа.

У нас Carbonio. Эти же настройки подойдут?

Регекспы и логика джейлов — да, форматы записей в журнале аутентификации там унаследованы от Zimbra. Но есть одна ловушка, на которой спотыкаются все: fail2ban на Carbonio по умолчанию не видит логи, и джейл запускается с нулём банов при полном журнале неудачных входов. Лечится строкой backend = polling в описании джейла. Симптом при этом ровно такой же, как при неправильно написанном регекспе, поэтому первым делом прогоняйте fail2ban-regex по реальному файлу лога — она сразу покажет, есть ли совпадения.

Стоит ли просто закрыть админку на 7071 снаружи?

Стоит, и это надо сделать в первую очередь, до всякого fail2ban. Консоль администратора не нужна из интернета никому — доступ к ней организуется через VPN или через список разрешённых адресов на файрволе. Перебор по 7071 в моём случае шёл параллельно с перебором веб-почты, и там подбирали именно аккаунт admin. Джейл на админку с maxretry=3 и баном на неделю мы всё равно оставили — как второй рубеж на случай, если правило на файрволе однажды кто-нибудь отредактирует не глядя.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#Zimbra#fail2ban#брутфорс#безопасность#логи
Комментарии 0

Оставить комментарий

загрузка...

Подпишитесь на рассылку ITfresh

Раз в неделю — практические гайды для руководителя IT и сисадмина: безопасность, 1С, миграции, резервные копии, лайфхаки из реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.