Пом'якшення DDoS і flood-атак на SMTP: anvil у Postfix, ліміти з'єднань і захист submission
DDoS на пошту — це майже завжди не завалений канал, а вичерпані smtpd-процеси й brute-force на 587. Розбираємо, як налаштувати вбудований rate-limiter Postfix (anvil), розвести ліміти на 25 і submission, поставити postscreen та fail2ban і де починається межа мережевого шару.
EvilMail Team17 липня 2026 р.12 хв читання
Коли о третій ночі прилітає алерт «поштова черга росте, LA 40», перше, що роблять недосвідчені — біжать перевіряти ширину каналу. Канал вільний. iftop показує пару мегабіт. А сервер лежить. Тому що DDoS на SMTP майже ніколи не про смугу пропускання. Це про вичерпання ресурсів: тисячі дешевих напіввідкритих сесій з'їдають слоти smtpd-процесів, ботнет ганяє brute-force по 587, а повільні клієнти хвилинами тримають з'єднання відкритими. І найгірше — типовий anti-DDoS перед HTTP тут не поможе взагалі, бо SMTP-діалог не проходить через Cloudflare.
Хороша новина: Postfix уже має вбудований rate-limiter — сервіс anvil — і десяток директив smtpd_*_limit, якими 90% таких атак гаситься без жодного стороннього заліза. Погана: дефолти Postfix для цього надто ліберальні, а submission-порт часто стоїть з тими самими налаштуваннями, що й вхідний 25. Розберемо, як зробити правильно.
Що насправді атакують, коли «ддосять» пошту
Забудьте про об'ємний флуд. У SMTP є три реальні вектори, і жоден з них не про гігабіти:
Connection flood. Тисячі TCP/SMTP-сесій, часто напіввідкритих (SYN без завершення хендшейку або CONNECT без EHLO). Кожна займає слот у пулі smtpd. Дефолтний
DDoS на SMTP: anvil, ліміти з'єднань і захист submission у Postfix — EvilMail Blog
default_process_limit = 100
означає, що атакеру достатньо сотні паралельних конектів, щоб забити весь пул — і легітимна пошта отримує
421 Server busy
.
Command flood / slow SMTP. Slowloris-подібне утримання сесії: повільний DATA по байту, нескінченні NOOP/RSET. З'єднання живе, слот зайнятий, корисної роботи нуль.
Auth brute-force. Ботнет довбить AUTH LOGIN на 587/465, перебираючи паролі ваших користувачів. Ціль не покласти сервер, а знайти слабкий пароль і почати з нього спамити.
Спільний знаменник — resource exhaustion, а не bandwidth exhaustion. Саме тому HTTP-орієнтований anti-DDoS марний: WAF і скрабінг-провайдери розбирають HTTP-запити, а не SMTP-діалог на 25 порту. Захист має жити на самому хості й на мережевому шарі перед ним.
Анатомія Postfix: чому дефолти течуть
Кількістю процесів на сервіс керує master.cf — колонка maxproc навпроти кожного рядка. Порожня колонка означає, що сервіс успадковує default_process_limit, тобто 100. У підсумку smtp на 25 і submission на 587 за замовчуванням конкурують за один пул зі ста процесів. Забив атакер вхідний 25 — впав і submission, і ваші користувачі більше не можуть відправити пошту.
Перше, що робимо при інциденті, — дивимось, що реально відбувається. Базовий набір діагностики:
bash
# Скільки напіввідкритих сесій висить на 25 порту
ss -tn state syn-recv '( sport = :25 )' | wc -l
# Активна конфігурація без дефолтів
postconf -n
# Розмір черги (останній рядок = скільки в черзі)
postqueue -p | tail -1
# Ознаки connection-flood у логах
grep "lost connection after CONNECT" /var/log/mail.log | tail -50
journalctl -u postfix@- --since "5 min ago" | grep -Ei "too many|lost connection|421"
Типова картина атаки в mail.log:
text
postfix/smtpd[21877]: connect from unknown[203.0.113.44]
postfix/smtpd[21877]: lost connection after CONNECT from unknown[203.0.113.44]
postfix/smtpd[21901]: warning: 198.51.100.7: too many connections
postfix/postscreen[21930]: NOQUEUE: reject: RCPT from [45.83.66.12]: 550 5.7.1 ...
Сотні lost connection after CONNECT за хвилину з різних IP — це connection-flood. warning: too many connections — це вже спрацював ліміт anvil, добре. Якщо його немає, а сервер лежить, то anvil у вас або вимкнений, або стоїть з дефолтним лімітом 50 конектів на клієнта, що для розподіленого ботнета марно.
anvil: rate-limiter, який усі забувають увімкнути
anvil — окремий сервіс у master.cf (рядок anvil ... anvil), який веде per-client лічильники: скільки одночасних з'єднань, скільки нових конектів за одиницю часу, скільки повідомлень і спроб AUTH. Сам він нічого не блокує — лише рахує, а ліміти задаються директивами в main.cf. Вікно рахунку — anvil_rate_time_unit, за замовчуванням 60 секунд.
Дефолти навмисне м'які, щоб не ламати легітимний трафік:
smtpd_client_connection_count_limit = 50 — одночасні з'єднання з одного IP
smtpd_client_connection_rate_limit = 0 — нові конекти за хвилину, 0 = вимкнено
smtpd_client_message_rate_limit = 0 — повідомлень за хвилину, вимкнено
Для вхідного 25 порту робочий, але обережний блок у main.cf:
ini
# Глобальні ліміти (застосуються до :25; submission перекриємо окремо)
anvil_rate_time_unit = 60s
smtpd_client_connection_count_limit = 30
smtpd_client_connection_rate_limit = 60
smtpd_client_message_rate_limit = 100
# НЕ ріжте власні мережі, MX-релеї й моніторинг
smtpd_client_event_limit_exceptions = $mynetworks 127.0.0.0/8 [::1]/128
Ключове застереження: anvil — це захист від одного гучного клієнта, а не від розподіленого ботнета. Тисяча IP, кожен з яких робить по 5 конектів, спокійно проходить під будь-яким per-client лімітом. Тому anvil — це база, а далі йдуть postscreen, fail2ban і мережевий шар.
Порт 25 проти submission: чому ліміти дзеркальні
Найпоширеніша помилка — однакові жорсткі ліміти на 25 і 587. На вхідному 25 клієнт — це чужий MX. Google і Microsoft шлють з великих пулів IP, відкриваючи багато паралельних з'єднань. Поставите агресивний connection_rate_limit на 25 — почнете відбивати 421 серверам Gmail, і ваша вхідна пошта поїде в затримки й баунси. На 25 порту ліберальність — це фіча.
На submission (587) і SMTPS (465) усе навпаки. Клієнт тут — ваш власний автентифікований користувач. Легітимному Thunderbird не потрібно 30 паралельних конектів; йому вистачить трьох. Тому на submission можна і треба бути суворим. Робимо це через per-service override у master.cf директивою -o:
Три речі тут критичні. smtpd_tls_security_level=encrypt разом зі smtpd_tls_auth_only=yes забороняють передавати пароль по відкритому каналу — жодного plaintext-AUTH. smtpd_client_restrictions=permit_sasl_authenticated,reject рубає з'єднання ще до RCPT, якщо клієнт не автентифікувався, тому connection-flood без валідного логіна відсікається дешево. А smtpd_delay_reject=no дозволяє відбити аноніма одразу після CONNECT, не проганяючи весь діалог.
Захист AUTH: postscreen на 25 і fail2ban на submission
Дві різні лінії для двох різних портів.
postscreen ставиться тільки на 25 (ніколи на submission!). Він працює до того, як з'єднання дійде до smtpd: тримає легкий процес, що робить DNSBL-перевірку й протокольні тести, і пускає до реального smtpd лише тих, хто пройшов. Ботнет-зомбі, які світяться в Spamhaus, відсіюються, не займаючи жодного smtpd-слота, — це прямий захист від connection-flood ботнетом. У master.cf рядок 25 стає smtp inet ... postscreen, а в main.cf:
Ваги (*2, *1) сумуються; поріг threshold = 2 означає, що самого лише Spamhaus zen достатньо для блокування, а дрібніші списки додають балів. greet_action = enforce перевіряє, чи клієнт не починає говорити до банера, — класичний маркер спам-бота. Deep-тести відкидають ботів, які не тримають протокол, але спрацьовують лише разом зі своїм *_action = enforce: з дефолтним ignore вони тільки пишуть у лог.
fail2ban ставиться на submission проти brute-force AUTH. Filter ловить рядки SASL LOGIN authentication failed у mail.log. Робочий jail:
Три невдалі спроби за 10 хвилин — бан на годину. Хто повертається знову й знову (5 банів за добу), того підхоплює recidive і банить на тиждень. Перевірка:
bash
fail2ban-client status postfix-sasl
fail2ban-client set postfix-sasl unbanip 203.0.113.44
Коли Postfix уже не тягне: мережевий шар
Чесна межа: коли розподілений SYN/connection-flood за обсягом нових з'єднань перевищує можливості хоста, лічильники Postfix не врятують — процеси створюються повільніше, ніж прилітають конекти. Тут потрібен L3/L4 фільтр раніше по стеку, у nftables:
nft
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif "lo" accept
# Захист від SYN-flood: ріжемо шквал нових SYN на поштові порти
tcp dport { 25, 587, 465 } tcp flags & (fin|syn|rst|ack) == syn \
ct state new limit rate over 60/second drop
# Per-IP ліміт нових конектів на submission (свій юзер стільки не робить)
tcp dport 587 ct state new \
meter subm { ip saddr limit rate 15/minute } accept
tcp dport { 25, 465 } ct state new accept
}
}
meter тримає лічильник на кожен вихідний IP окремо, тому 15/minute обмежує одного клієнта, а не весь сервер, — глобальний limit rate без meter тут поклав би доставку всім юзерам одразу. Плюс sysctl-тюнінг, без якого conntrack-таблиця переповнюється й сервер починає дропати вже все підряд:
Якщо й цього мало — наступний рівень це haproxy перед Postfix з maxconn і rate-limit sessions або винесення MX за скрабінг-провайдера, що вміє чистити L3/L4 (не HTTP-WAF). Повторю головне: L7 anti-DDoS для вебу тут не працює, бо він не розуміє SMTP-діалог і не стоїть на шляху 25/587.
Чеклист бойового налаштування
anvil увімкнено з ненульовими connection_count_limit і connection_rate_limit на 25.
Per-service ліміти на submission через -o у master.cf: connection_count_limit=10, auth_rate_limit=10.
Різні ліміти для 25 і 587 — на вхідному ліберальні, на вихідному жорсткі.
postscreen + DNSBL тільки на 25, dnsbl_action=enforce, deep-тести разом зі своїм *_action=enforce.
fail2ban postfix-sasl (maxretry=3, bantime=1h) + recidive на тиждень.
Власні мережі у `smtpd_client_event_limit_exceptions` — не заблокуйте свій моніторинг і релеї.
STARTTLS/implicit TLS обов'язкові на 587/465, smtpd_tls_auth_only=yes, plaintext-AUTH закритий.
sysctl: tcp_syncookies=1, підвищений nf_conntrack_max, per-IP nftables-меtер на нові конекти.
Моніторингss -tn state syn-recv sport = :25 | wc -l з алертом на різкий сплеск smtpd-процесів.
Ротація mail.log налаштована агресивно, щоб flood не забив диск за ніч і не поклав сервер уже логами.
Почніть з двох пунктів прямо зараз: розведіть ліміти на 25 і 587 і повісьте fail2ban на SASL. Це закриває найбільшу дірку — brute-force на submission — за п'ятнадцять хвилин, ще до того, як ви дійдете до nftables і postscreen.