Ловим взломанный ящик по логам: аномальные логины, всплеск исходящих и авто-алерты
Взломанный ящик не падает с ошибкой — он тихо работает как чужой SMTP-релей, пока не прилетит блок от Spamhaus. Разбираем три конкретных паттерна в логах Dovecot и Postfix и вешаем на них дешёвый автодетектор на bash и cron — без дорогого SIEM.
EvilMail Team23 июля 2026 г.11 мин чтения
В 03:14 ящик [email protected] залогинился с IP в Хетцнере и за сорок минут отправил 1 900 писем. К 06:00 отправляющий IP улетел в чёрные списки Spamhaus (CBL/XBL), репутация всего /24 просела, легитимная почта клиентов отбивалась с 550 5.7.1, а телефон поддержки раскалился. Классика.
Самое обидное: сигнал лежал в mail.log уже в 03:14. Не в 06:00, когда прилетел блок, а почти на три часа раньше. Взломанный ящик почти никогда не «ломается» с ошибкой — он аккуратно проходит SASL-аутентификацию с валидным (утёкшим) паролем и работает как чужой релей, пока внешний мир не заметит спам-волну и не сожжёт репутацию IP. К этому моменту чинить уже поздно.
Хорошая новость: чтобы поймать этот момент, не нужен SIEM за $$$. Нужно знать три паттерна и повесить на них 40 строк bash. Разберём каждый на реальных строках лога.
Три сигнала, которые выдают чужого в ящике
Компрометацию выдают три измеримых индикатора, и все три уже лежат в ваших логах:
Успешный логин с аномального источника
— новый IP/ASN/страна для конкретного пользователя, либо «impossible travel»: один и тот же
user
заходит из двух разных ASN за 15 минут.
Резкий рост исходящих по sasl_username относительно его же baseline. Важно не абсолютное число, а отклонение от нормы конкретного ящика.
Изменение поведения ящика — появление auto-forward/redirect в sieve или смена пароля вне рабочего окна.
Обратите внимание: смотрим на успешные логины, а не на brute-force. Это принципиально. fail2ban ловит перебор паролей и отлично блокирует ботов, которые долбят AUTH тысячу раз в минуту. Но реальная компрометация в 2026 году — это чаще всего утёкший или зафишенный пароль, который срабатывает с первой попытки. Для fail2ban такой вход неотличим от захода легитимного владельца. Валидный пароль плюс аккуратный спам он не видит в принципе.
Читаем auth-логи Dovecot: кто и откуда логинится
Успешный вход в IMAP выглядит так (Debian, /var/log/mail.log; на RHEL это /var/log/maillog, либо journalctl -u dovecot):
Нас интересуют два поля: user=<...> и rip= (remote IP). Первый однострочник — топ пользователей по числу логинов за сутки, чтобы глазом зацепить аномалию:
Дальше — список уникальных IP конкретного ящика. Владелец обычно ходит с двух-трёх адресов (дом, офис, мобильный оператор). Внезапный четвёртый из чужой сети — повод присмотреться:
Красные ASN для человеческого ящика: AS24940 (Hetzner), AS14061 (DigitalOcean), AS16276 (OVH). Клиент с телефона Мегафона в этих сетях не появляется — а вот арендованная спамером VPS появляется всегда.
Impossible travel — самый чистый сигнал. Один user, два rip в разных ASN за интервал меньше 15 минут. Физически невозможно: человек не телепортируется из Москвы во Франкфурт за десять минут. Отдельно стоит считать долю method=PLAINбезTLS в строке — легитимные клиенты давно шифруют submission, а голый PLAIN часто выдаёт скриптованного отправителя, тянущего пароль из дампа.
Главная ошибка новичков — абсолютный порог вроде «алерт при 200 письмах/час». Он одновременно и слишком высокий, и слишком низкий. У вашего рассыльщика новостей 500 писем/час — норма, порог 200 будет орать каждый день, вы его отключите. А у бухгалтера 20 писем в день — и его взломанный ящик, разгоняющий 150 писем/час, спокойно проедет под порогом 200.
Правильно — baseline на пользователя. Берём p95 почасовых исходящих за 14 дней и триггерим при превышении в 3 раза. Тогда детектор ловит и тихого бухгалтера (у него 3x — это уже 60/ч), и не будит вас из-за рассыльщика.
Дополнительные красные флаги в той же выборке: рост NOQUEUE: reject, множество status=bounced на чужие домены получателей (спамер шлёт по купленной базе — половина адресов мёртвые), отправка в 03:00 при дневном профиле активности. Для быстрой ручной сводки держите под рукой pflogsumm:
bash
pflogsumm -d today /var/log/mail.log | less
Скрипт-детектор и авто-алерт на cron
Собираем логику в один разбор. Запуск каждые 5 минут через cron, состояние в /var/lib/mailwatch/, чтобы не слать один и тот же алерт двадцать раз подряд. Окно и порог — в переменных сверху. Вместо форка date на каждую строку лога собираем grep-шаблон из минутных меток последнего окна — так детектор не захлёбывается на многогигабайтном mail.log:
bash
#!/usr/bin/env bash
# /usr/local/bin/mailwatch.sh — окно WINDOW минут, порог на окно
set -euo pipefail
LOG=/var/log/mail.log
WINDOW=5 # минут в окне
THRESHOLD=25 # писем за окно на sasl_username (≈300/ч)
STATE=/var/lib/mailwatch
HOOK="https://hooks.slack.com/services/XXX/YYY/ZZZ"
ALERT_TO="[email protected]" # адрес ВНЕ взломанного домена
mkdir -p "$STATE"
find "$STATE" -type f -mmin +120 -delete # чистим старые флаги
# шаблоны "Jul 8 03:14" для каждой из последних WINDOW+1 минут
pat=$(for i in $(seq 0 "$WINDOW"); do date -d "-$i min" '+%b %e %H:%M'; done \
| paste -sd'|')
matches=$(grep -E "^(${pat})" "$LOG" \
| grep -oP 'sasl_username=\K\S+' | sort | uniq -c || true)
[ -z "$matches" ] && exit 0
while read -r count user; do
(( count < THRESHOLD )) && continue
flag="$STATE/${user//\//_}"
[ -f "$flag" ] && continue # уже алертили — молчим
touch "$flag"
msg="mailwatch: $user — $count писем за ${WINDOW}м на $(hostname)"
curl -sf -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"$msg\"}" "$HOOK" || true
printf '%s\n' "$msg" | mail -s "COMPROMISE ALERT: $user" "$ALERT_TO"
done <<< "$matches"
cron
*/5 * * * * root /usr/local/bin/mailwatch.sh
Два нюанса, которые ломают такие детекторы на проде. Первый — алерт-адрес обязан быть вне скомпрометированного домена. Если soc@ живёт на том же сервере, атакующий с доступом к ящику первым делом добавит sieve-правило, которое дропает ваши же уведомления. Второй — touch "$flag" глушит шторм дублей, но флаги нужно чистить: find "$STATE" -mmin +120 -delete в начале прогона возвращает ящик под наблюдение через два часа, иначе повторный инцидент вы просто не увидите.
Не хотите писать сами — возьмите готовые кирпичи. rspamd умеет ratelimit и детект пересылки, fail2ban закрывает brute-force, postfwd даёт policy-лимиты. Но помните ограничение: ни один из них из коробки не ловит сценарий «правильный пароль + аккуратный спам». Собственный детектор по baseline тут не роскошь, а необходимость.
Немедленная реакция: обрубаем сессии, потом чиним
Порядок действий критичен. Сначала обрубить доступ, потом расследовать — иначе атакующий дописывает sieve-правила быстрее, чем вы их чистите.
bash
# 1. Кто сейчас в ящике
doveadm who [email protected]
# 2. Выкинуть все активные сессии
doveadm kick [email protected]
# 3. Сменить пароль (в проекте схема PLAIN, uid/gid 5000;
# для crypt-схем: doveadm pw -s SHA512-CRYPT)
# затем UPDATE virtual_users в mailserver-БД
# 4. Вычистить спам из очереди — только письма этого отправителя
mailq | awk 'BEGIN{RS=""} /sales@evilmail\.pro/{print $1}' \
| tr -d '*!' | postsuper -d -
postqueue -p | tail -1 # проверить, сколько осталось
# 5. Проверить sieve на тихую пересылку/vacation
doveadm sieve list -u [email protected]
doveadm sieve get -u [email protected] <script_name>
Пятый шаг пропускают чаще всего, а он — главный. Классический ход атакующего: не менять пароль (чтобы владелец ничего не заметил), а поставить тихий redirect на свой gmail. Дальше он читает всю входящую почту жертвы месяцами — сбросы паролей, счета, переписку — даже после того, как вы «почистили» ящик. Если в ~/.dovecot.sieve или /var/mail/vhosts/<domain>/<user>/sieve появился незнакомый redirect/fileinto на внешний адрес — это не про спам, это про кражу данных. Заодно ревизуйте app-пароли и OAuth-гранты: смена основного пароля их не отзывает.
Профилактика: rate-limit до того, как станет поздно
Детектор ловит инцидент, но лимит его предотвращает. Поставьте postfwd перед smtpd и режьте по sasl_username:
Правило: больше 60 писем в час на один SASL-логин — Postfix отвечает 450 (temporary defer). Легитимный пользователь притормозит на минуту и не заметит; взломанный ящик упрётся в потолок и не успеет сжечь репутацию до срабатывания алерта. Для отдельных рассыльщиков поднимите лимит адресно.
Базовая гигиена, которая закрывает вектор входа: обязательный TLS на submission (587/465), отключение legacy IMAP без шифрования, 2FA либо app-пароли вместо основного, и — отдельными событиями — алерт на смену пароля и на появление нового sieve-redirect.
Чек-лист
Baseline снят — p95 почасовых исходящих на пользователя за 14 дней, обновляется еженедельно.