[Часть 3] Мейл-бомба через Google Groups: когда антиспам работает против вас
Разбирая логи почтового сервера по другому поводу, я наткнулся на четырёхминутный всплеск: пятнадцать писем на один адрес. Дальше стало интереснее.
Как это выглядит в логе
from=<pz+bncAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@attacker.example>
from=<pz+bncBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB@attacker.example>
from=<pz+bncCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC@attacker.example>
...
Отправители с одного домена, все через mail-*.google.com, все на один мой адрес. Префикс pz+bnc — это VERP-формат Google Groups, служебный bounce-адрес рассылочного списка.
Темы писем выдают схему целиком:
"Your request has been received" (Zendesk)
"Potwierdzenie otrzymania wiadomości: NNNNNNNN"
"Ihre Kontaktanfrage an Mars Petcare"
"Servicecenter DB Regio Bus BaWü" (Deutsche Bahn)
"Zenzero ticket #NNNNNNN has been created"
"Encerramento do atendimento - Ticket NNNNNNNNNN" (Odoo)
"Revalize Customer Success Case #NNNNNNNN" (Salesforce)
"[Aktion erforderlich] Bestätigen Sie Ihre E-Mail" (Amazon SES)
Автоответы хелпдесков со всего мира. Немецкие, польские, португальские, испанские.
Механика атаки
Это subscription bombing, он же list bombing. Устроен так:
- Атакующий создаёт группу в Google Groups на своём домене.
- Вписывает туда адрес жертвы.
- Массово шпигует адресом группы контактные формы, регистрации и хелпдески по всему интернету.
- Все автоответы «мы получили вашу заявку» приходят в группу и веером расходятся по её участникам — то есть жертве.
Прелесть схемы для атакующего в том, что весь трафик идёт от легитимных отправителей: Zendesk, Salesforce, Odoo, корпоративные хелпдески. У них корректный SPF, валидный DKIM, хорошая репутация IP. Блокировать их по источнику невозможно — это настоящие письма от настоящих компаний.
Почему не сработал SpamAssassin
Вот тут началось самое неприятное. Смотрю оценку:
Hits: -1.204
Tests: [DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,
DKIM_VALID_EF=-0.1, DMARC_MISSING=0.001, HTML_MESSAGE=0.001,
MAILING_LIST_MULTI=-1, RCVD_IN_DNSWL_BLOCKED=0.001,
RCVD_IN_MSPIKE_H2=-0.01, RCVD_IN_ZEN_BLOCKED_OPENDNS=0.001,
SPF_HELO_NONE=0.001, SPF_PASS=-0.001, URIBL_BLOCKED=0.001]
autolearn=ham
Минус 1.2. Письмо не просто прошло — оно прошло с запасом и было заучено как ham, то есть как эталон хорошей почты.
Разберём, откуда минус.
MAILING_LIST_MULTI = −1
Правило срабатывает, когда в письме несколько заголовков рассылочного списка. Задумано разумно: легитимные рассылки не должны попадать в спам из-за формата, и им дают компенсирующий бонус.
Атакующий использует ровно этот механизм. Письма действительно идут из рассылочного списка — настоящего Google-группового. Формально правило право. Практически оно даёт бомбе минус балл.
DKIM_VALID и SPF_PASS в минус
Тоже логично сами по себе: подписанное и аутентифицированное письмо заслуживает доверия. Но отправители здесь — Google, Salesforce, Amazon SES. У них с аутентификацией всё идеально.
_BLOCKED_OPENDNS в нулях
RCVD_IN_ZEN_BLOCKED_OPENDNS, URIBL_BLOCKED, URIBL_DBL_BLOCKED_OPENDNS — суффикс означает «запрос отклонён». Репутационные проверки Spamhaus и URIBL на моём сервере вообще не выполнялись (об этом отдельная история). Проверка доменов в ссылках — та, которая могла бы что-то заметить — не работала.
Итог: сумма положительных признаков около нуля, сумма бонусов — минус единица. Фильтр честно посчитал письмо хорошим.
Вывод, который стоит запомнить: контентный антиспам не защищает от атак объёмом. Каждое письмо по отдельности легитимно. Проблема в их количестве, а SpamAssassin оценивает письма поштучно.
Что делать
1. Обнулить вредный бонус
# /etc/mail/spamassassin/local.cf
score MAILING_LIST_MULTI 0
Правило перестаёт влиять в обе стороны. Легитимные рассылки от этого не пострадают — у них хватает других положительных признаков, а вот эксплуатировать бонус больше не выйдет.
systemctl restart amavis # в iRedMail SA живёт внутри amavisd
2. Заблокировать VERP-паттерн
В iRedMail уже есть готовая точка входа — check_sender_access pcre: в цепочке ограничений:
postconf -n | grep smtpd_sender_restrictions
smtpd_sender_restrictions = reject_non_fqdn_sender reject_unlisted_sender
permit_mynetworks permit_sasl_authenticated
check_sender_access pcre:/etc/postfix/sender_access.pcre
reject_unknown_sender_domain
Дописываем в /etc/postfix/sender_access.pcre:
/^pz\+bnc[A-Za-z0-9]+@/ REJECT Blocked sender
/@(.*\.)?0335boli\.com$/ REJECT Blocked sender
PCRE-таблицы не требуют postmap, достаточно postfix reload.
Проверить:
postmap -q "pz+bncAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@attacker.example" pcre:/etc/postfix/sender_access.pcre
REJECT Blocked sender
postmap -q "normal@gmail.com" pcre:/etc/postfix/sender_access.pcre
# пусто
Первое правило важнее второго: оно ловит формат VERP Google Groups, а не конкретный домен. Атакующий сменит группу — правило продолжит работать.
Перед применением стоит убедиться, что вы не состоите в легитимных Google-группах:
grep "pz+bnc" /var/log/mail.log* | grep -v <домен_атаки> | head
Отбой происходит на этапе MAIL FROM, до приёма тела письма — не тратится трафик и не грузится amavis. И check_sender_access стоит после permit_mynetworks permit_sasl_authenticated, так что на свою отправку не влияет.
3. Ограничить объём
Блэклист лечит текущую атаку. От следующей защищает rate limit на получателя — потому что ограничивает саму форму атаки, а не конкретного отправителя.
В iRedMail для этого есть iRedAPD с плагином throttle. Правило пишется в БД:
INSERT INTO throttle (account, kind, period, priority, max_msgs, max_quota, msg_size)
VALUES ('victim@example.com', 'inbound', 3600, 100, 100, -1, -1);
Сто писем в час на адрес. Обычной переписке не мешает, лавину срезает независимо от того, с какого домена она льётся.
У меня, правда, обнаружилось, что iRedAPD в этот момент вообще не работал — но это тема отдельной статьи.
Что ещё важно проверить
Subscription bombing редко бывает самоцелью. Классический сценарий: атакующий заваливает ящик мусором, чтобы среди сотен писем вы не заметили уведомление о смене пароля, списании средств или входе в аккаунт.
Так что параллельно с блокировкой стоит:
grep -iE "password|reset|подтверд|вход|login" /var/log/mail.log | grep <ваш_адрес>
И вручную просмотреть почту за период атаки: не затерялось ли между мусором письмо от банка, регистратора доменов, хостинга или госуслуг. Проверить активные сессии в основных сервисах.
В моём случае ничего подозрительного не нашлось — но не проверить было бы неправильно.
Если адрес нужен
Соблазн просто удалить скомпрометированный алиас велик, и если он не используется — так и надо сделать. Адрес уже в списках у бомберов, и это не лечится.
Если адрес нужен для работы, вариантов три, по возрастанию надёжности:
- Блэклист по источнику — играть в догонялки со сменой доменов.
- Карантин — не удалять, но складывать в отдельную папку правилом Sieve. Бомба не мешает работе.
- Rate limit — ограничить приём. Защита от формы атаки, а не от участников.
Лучше всё вместе.
Мораль
Три вещи, которые я вынес из этой истории.
Первое. Байесовский фильтр и правила по содержимому бессильны против атаки объёмом. Каждое письмо легитимно, аутентифицировано и осмысленно. Нужен механизм другого уровня — лимит на скорость приёма.
Второе. Компенсирующие правила антиспама — потенциальный вектор. MAILING_LIST_MULTI создан, чтобы не резать легитимные рассылки, и он же превращает бомбу в письма с отрицательным баллом. Стоит просматривать список правил с отрицательными весами и думать, что будет, если кто-то начнёт их эксплуатировать намеренно.
Третье, и главное. Атака пришла ровно в тот период, когда у меня одновременно не работали Spamhaus в SpamAssassin, iRedAPD с его throttle и greylisting. Ни один из этих отказов не породил алерта.
Совпадение или нет — сказать не берусь. Но выглядит поучительно: защита, о состоянии которой вы не знаете, эквивалентна её отсутствию.