Виявлення зламаних поштових акаунтів: сигнали в логах, аномальні обсяги відправки і автоблокування
Компрометація скриньки — це не «вгадали пароль», а тихий вхід з валідними кредами і розсилка спаму ще до першої скарги. Розбираємо, як за 5-10 хвилин відловити зламаний акаунт по сигнатурах у maillog і побудувати автоблокування, що ріже сесію на рівні Dovecot і Postfix за секунди.
EvilMail Team16 липня 2026 р.11 хв читання
Зламаний поштовий акаунт майже ніколи не виглядає як зламаний. Немає червоної лампочки, немає падіння сервісу. Є валідний SASL-логін, є нормальна TLS-сесія на 587-му, і є акаунт, який раптом почав слати. До моменту, коли ти дізнаєшся про це з тікета «наші листи йдуть у спам», твій вихідний IP уже висить у Spamhaus CSS або Barracuda BRBL, а доставність просіла для всіх абонентів на тому ж relay. Один зламаний ящик — і ти тиждень чистиш репутацію цілої /24 підмережі.
Тому детект компрометації — це не про антивірус і не про «складніші паролі». Це про те, щоб читати maillog як кардіограму і мати автоматику, яка ріже сесію швидше, ніж атакер встигає вигребти чергу. Далі — як це виглядає в реальному часі і як це ловити.
Як виглядає зламаний акаунт у логах за перші хвилини
Компрометація має характерну часову сигнатуру. Спочатку — розвідка боєм: серія невдалих аутентифікацій з одного IP, часто перебір по словнику або credential stuffing зі злитих баз.
Як виявити зламаний поштовий акаунт: логи, аномальні обсяги, автоблок — EvilMail Blog
Зверни увагу на третій рядок. Успішний Login приходить з іншого IP і часто з іншого ASN, ніж серія фейлів. Це класика: брутфорс іде з одного ботнет-вузла, а фінальний валідний вхід — з residential-проксі або зовсім іншої країни, щоб обійти geo-фільтри. За 60-120 секунд після цього логіну з'являється перший вихідний потік:
Маркер компрометації — не сам вхід, а різка зміна поведінки. Акаунт, який три роки тільки читав пошту через IMAP з одного домашнього IP, раптом:
логіниться з нового ASN (стрибок geo);
має новий User-Agent IMAP-клієнта;
відкриває вихідний SMTP-потік, якого в його історії не було зовсім.
Ключове слово тут — baseline. Без нормального профілю відправки для кожного юзера будь-який поріг перетворюється на вгадування. «100 листів за годину» вб'є легітимну розсилку клієнта і пропустить акаунт temp-mail, який у нормі не шле нічого, а тепер видав 48 адресатів за один заліт.
Читаємо сигнали: grep, який реально працює
Перше, що ти робиш при підозрі, — витягуєш карту «хто звідки логінився». Один SASL-username з кількох ASN за годину — майже завжди компрометація (або юзер із кривим мобільним роумінгом, тому й потрібен обсяг як другий сигнал).
bash
# Успішні SMTP AUTH: username → унікальні IP за останню годину
grep 'sasl_username=' /var/log/mail.log \
| grep "$(date '+%b %e %H')" \
| sed -nE 's/.*client=([0-9.]+).*sasl_username=([^ ,]+).*/\2 \1/p' \
| sort -u | awk '{c[$1]++} END{for(u in c) if(c[u]>2) print c[u], u}' \
| sort -rn
Якщо в юзера більше двох унікальних IP за годину — це в топі списку на розбір. Далі дивимось обсяг по черзі:
bash
# Топ відправників за кількістю адресатів (nrcpt) з qmgr
grep 'postfix/qmgr' /var/log/mail.log \
| sed -nE 's/.*from=<([^>]+)>.*nrcpt=([0-9]+).*/\1 \2/p' \
| awk '{sum[$1]+=$2} END{for(a in sum) print sum[a], a}' \
| sort -rn | head
Розрізняй два принципово різні сигнали. auth failed ... 15 attempts — це просто брутфорс, шум, його ллється тонни, сам по собі він не інцидент. А от успішний `plain login` одразу після серії фейлів з того ж або близького вектора — це успішний перебір, і це вже інцидент. Різницю ловиш кореляцією по username у вікні кількох секунд.
Для geo/ASN не бий по зовнішньому API — на сплеску тебе зарейтлімлять, і скрипт залипне. Тримай локальну базу MaxMind GeoLite2 і резолви офлайн:
IMAP-сесії, User-Agent і активні підключення дивишся через doveadm who — це дає моментальний зріз, хто зараз висить на скриньці і з яких IP.
Три метрики, за якими ловиться компрометація
Один поріг ніколи не працює. Робочий детект стоїть на трьох осях одночасно, і аларм — це коли сходяться щонайменше дві.
1. Обсяг за ковзне вікно проти персонального baseline. Не глобальний поріг, а RCPT/год конкретного юзера проти його ж середнього за 30 днів. Аларм, якщо поточний темп перевищує baseline у 5+ разів. Для акаунта, який у нормі шле 3 листи на день, 48 адресатів за хвилину — це x100, миттєвий тригер. Для акаунта розсилки з baseline 2000/год той самий обсяг — норма.
2. Bounce / rejection ratio. Зламані скриньки шлють на брудні куплені списки, тому частка 5xx і user unknown злітає до 30-60% за перші хвилини. Легітимна пошта тримається нижче 2-5%. Ratio-сигнал спрацьовує раніше за абсолютний обсяг, бо погана база палиться з перших сотень листів, ще до того, як накопичиться помітний тоннаж.
3. Розсіювання одержувачів. Багато різних доменів-получувачів за короткий проміжок = розсилка, а не листування. Більше 20 унікальних доменів за 10 хвилин від акаунта без історії масової відправки — сильний маркер. Людина пише 3-5 контрагентам, а не в 200 доменів поспіль.
Числа-орієнтири (їх обов'язково калібрувати під тариф): аларм при RCPT/год > 5x baseline; bounce ratio > 25-30% за 15 хв; > 20 унікальних доменів-одержувачів за 10 хв; логін з нового ASN + вихідний потік у межах 2 хв. На temp-mail/SMB-провайдері ці пороги агресивніші, бо legit-відправки мало; на бізнес-тарифі з розсилками — м'якші, інакше засиплеш false positive.
Автоблокування: рубати сесію, а не писати тікет
Детект без автоматичної реакції марний: поки черговий прочитає алерт, атакер вигребе 20 тисяч листів. Реакція має бути машинною і йти ланцюгом.
Порядок дій при спрацюванні hard-block:
bash
# 1. Вибити всі активні сесії юзера з Dovecot (IMAP/POP/submission)
doveadm who -1 [email protected]
doveadm kick [email protected]
# 2. Заморозити (hold) ЛИШЕ листи цього відправника, не чіпаючи чужу пошту.
# postqueue -j дає JSON по кожному листу (Postfix 3.1+).
postqueue -j | jq -r 'select(.sender=="[email protected]") | .queue_id' \
| xargs -rn1 postsuper -h
Далі — відключення SMTP AUTH на рівні бази. У нас схема mailserver.virtual_users (Dovecot, PLAIN, uid/gid 5000), тому додаємо прапорець і зав'язуємо на нього Postfix:
sql
ALTER TABLE virtual_users ADD COLUMN sending_blocked BOOLEAN DEFAULT false;
UPDATE virtual_users SET sending_blocked = true WHERE email = '[email protected]';
У Postfix прив'язку логіну до відправника роблять smtpd_sender_login_maps + reject_sender_login_mismatch, а сам блок — через policy-сервіс на smtpd_data_restrictions, який читає sending_blocked і повертає REJECT для заблокованих SASL-username. Policy-демон отримує на stdin атрибути (sasl_username, client_address, recipient_count) і при перевищенні швидкості відповідає action=DEFER_IF_PERMIT throttled — це і є soft-block.
Важливий інженерний вибір: soft-block через throttle кращий за повне видалення для боротьби з false positive. Якщо це виявилась легітимна розсилка, throttle просто уповільнив її на кілька хвилин, а не спалив клієнту кампанію. Для швидкісного ліміту на вході бери postfwd/policyd або нативні smtpd_client_message_rate_limit і anvil — вони дають per-user rate без окремого демона.
Прибирання після інциденту і закриття діри
Сесію ти зарубав — але робота лише почалась. По-перше, черга. Дивись mailq, релізь легітимне (postsuper -H <queueid>), явний спам видаляй точково по queue-id. Ніколи не роби postsuper -d ALL на живому релеї — вб'єш чужу пошту, що просто проходила поруч.
По-друге — і це найчастіше пропускають — перевір sieve-фільтри на підкинуте auto-forward. Класичний хід атакера: поставити приховане пересилання на зовнішній ящик, щоб продовжувати читати пошту навіть після скидання пароля. Шукай redirect/fileinto, яких там бути не повинно:
По-третє — форс-скидання пароля, інвалідизація всіх app-паролів і активних сесій, ротація OAuth-токенів. Пароль без цього марний: живі токени переживуть його зміну.
По-четверте — репутація. Перевір, чи не влетів вихідний IP у DNSBL, і подай сигнали через Postmaster Tools / SNDS:
І нарешті — розбір, як саме зайшли: reused-пароль зі злитої бази, фішинг, слабкий IMAP-пароль без 2FA. Без цього кроку інцидент повториться з тим самим вектором через тиждень.
Чеклист і пороги за замовчуванням
Логуй: SASL-username + client IP на кожен AUTH, nrcpt з qmgr, bounce-коди, ASN з локальної MaxMind. Без цих полів детекту не буде.
Пороги (калібрувати під тариф): RCPT/год > 5x персонального baseline; bounce ratio > 25-30% за 15 хв; > 20 унікальних доменів за 10 хв; новий ASN + вихідний потік < 2 хв.
Тригер = збіг двох осей, не однієї. Один geo-стрибок без обсягу — не привід для hard-block.
Порядок автодій: kick сесій → sending_blocked=true → postsuper -h → alert. Саме в такій послідовності: спершу перекрити кран, потім заморозити те, що вже впорснуто.
Soft-block перед hard-block для сумнівних кейсів — throttle, а не REJECT.
Тестуй правило на історичних логах відомої легітимної розсилки, перш ніж вмикати автоблок у прод.
Антипатерни, які гарантовано зроблять гірше: глобальний поріг «X листів на всіх» (вб'є легітимні акаунти й пропустить тихі); блок за одним geo-стрибком без обсягу (мобільний роумінг, VPN — і клієнт заблокований на рівному місці); видалення листів із черги без hold (втратиш легітимну пошту й доказову базу інциденту). Компрометацію ловить не строгість одного порога, а кореляція сигналів проти індивідуального baseline — і автоматика, що встигає спрацювати в те саме вікно у 2-4 хвилини, поки IP ще чистий.