VPN·HUB·CRACK Чистый egress: не дать ноде улететь в Spamhaus · VPN HUB CRACK
Все статьи
Инфраструктура и защита NEW PRO

Чистый egress: не дать ноде улететь в Spamhaus

Обновлено 2 сентября 2026 Проверено

Чистый egress: не дать ноде улететь в Spamhaus

Ваша нода пропускает трафик клиентов наружу. Среди клиентов однажды окажется один с заражённым устройством или спам-ботом, и тогда в чёрный список попадёт не он, а IP вашей ноды. Дальше письма от всех, кто ходит через эту ноду, начинают отбиваться, а половина сайтов показывает капчу.

Реальный случай из практики сервиса: у клиента на телефоне сидел банковский троян, который стучался на командный сервер по 443 порту. Spamhaus увидел это обращение с IP ноды и забанил адрес. Клиент ничего не подозревал, а сервис получил мёртвый IP из-за чужой малвари.

Ключевая мысль, с которой начинается защита: абуз через ноду бывает трёх видов, и лечатся они разными мерами. Одной кнопки нет.

Что идёт через ноду Куда бьёт Чем закрывается
Спам-бот SMTP, порт 25 (реже 465, 587) блок исходящего порта на ноде
Malware, обращение к C&C часто 443, по домену или по голому IP доменный блок-лист плюс IP-фид
Криптомайнер stratum, домены пулов доменный блок-лист

Дальше — по каждому.

Спам: закрыть исходящий SMTP

Это самая частая причина попадания в Spamhaus и самая простая в лечении.

Легитимному пользователю прямой SMTP на порт 25 не нужен. Его почтовый клиент отправляет письмо через submission своего провайдера — порт 587 с STARTTLS или 465 с TLS, с авторизацией. Доставка между серверами по 25 порту происходит уже между инфраструктурами провайдеров, а не с устройства человека. Прямое соединение с вашей ноды на чужой почтовый сервер по 25 почти всегда либо спам-бот, либо открытый релей.

Поэтому 25 порт на выход закрывается насовсем, без сожалений.

nft add table inet egress
nft add chain inet egress output '{ type filter hook output priority 0; policy accept; }'
# 25 — блок насовсем, легитимного применения с ноды нет
nft add rule inet egress output tcp dport 25 reject with tcp reset

С портами submission 465 и 587 тоньше. Ими иногда пользуются честно. Но на выходной ноде у всех клиентов один исходящий IP — IP самой ноды, — поэтому лимитировать «по клиенту» на уровне фаервола нельзя: источник один. Два разумных варианта:

Полный блок, как у 25. Так делает DigitalOcean для новых серверов — режет сразу все три порта. Самый безопасный вариант для инфраструктуры, где абуз одного бьёт по всем.

Общий лимит на узел — пропускает единичные отправки, режет массовую рассылку:

nft add rule inet egress output tcp dport { 465, 587 } ct state new limit rate 5/hour burst 5 accept
nft add rule inet egress output tcp dport { 465, 587 } reject with tcp reset

Правила не переживут перезагрузку сами по себе. На голых Ubuntu и Debian пакета iptables-persistent часто нет, и файл /etc/iptables/rules.v4 при загрузке никто не читает. Нужен свой systemd-юнит, который применяет правила при старте: Type=oneshot, RemainAfterExit=yes, After=network-pre.target. И зеркальте всё в ip6tables/nft ... ip6: забудете IPv6 — защита закроет ровно половину, а бот уйдёт по второй.

Второй слой — в самом Xray

Если нода в Docker в bridge-режиме, правила OUTPUT хоста могут не видеть трафик контейнера. Проверьте:

docker inspect remnanode --format '{{.HostConfig.NetworkMode}}'

Не host — добавьте блок ещё и внутри Xray, в routing ноды. Он матчит порт назначения независимо от домена и IP:

{ "type": "field", "port": "25", "network": "tcp", "outboundTag": "blackhole" }

Malware и C&C: два слоя, потому что одного мало

Здесь начинается то, ради чего вся статья. Троян на устройстве клиента обращается к командному серверу, и это обращение уходит с IP ноды. Блок порта 25 тут бесполезен: в кейсе с банковским трояном обращение шло на 443, как обычный HTTPS.

Значит нужен блок по адресу назначения. И тут ловушка: адрес назначения бывает двух видов, а блок-листы — тоже, и они не взаимозаменяемы.

Слой 1: доменный блок-лист на ноде

Готовый вариант, который уже гоняют в бою, — security.dat из репозитория Chocolate4U/Iran-v2ray-rules. Это доменный geosite-файл с тремя категориями: malware, phishing, category-ads-all. Источники — URLhaus от abuse.ch и списки фишинга, обновляется через GitHub Actions.

Кладёте .dat в папку geodata на ноде и добавляете правило в routing самой ноды:

{
  "type": "field",
  "domain": ["ext:security.dat:malware", "ext:security.dat:phishing"],
  "outboundTag": "blackhole"
}

⚠️ Важная оговорка про geodata. В клиентских конфигах мы geosite/geoip не используем намеренно: у части пользователей файл geosite.dat урезан, и ядро на клиенте падает. Но здесь файл лежит на вашей ноде, под вашим контролем и в полном виде. Клиента он не роняет, потому что правило выполняется на сервере. Это единственное место, где geodata в нашей схеме уместна.

Слой 2: IP-фид, потому что домена может не быть

Доменный список ловит C&C, только если малварь обращается к нему по имени и это имя видно Xray. А значительная часть троянов и майнеров ходит на голый IP — там нет доменного имени вообще, сравнивать нечего, и доменное правило такое соединение пропускает.

В том самом кейсе с tinba это и есть развилка: если троян стучал на C&C по домену — security.dat бы его поймал; если по IP — прошёл бы мимо, и нужен IP-блок.

IP-блок ставится на nftables отдельным набором, наполняемым внешним фидом. Он режет по адресу назначения на любом порту, независимо от протокола:

nft add table inet threat
nft add set inet threat c2ips '{ type ipv4_addr; flags interval; }'
nft add chain inet threat output '{ type filter hook output priority 0; policy accept; }'
nft add rule inet threat output ip daddr @c2ips drop

Наполнение из Feodo Tracker (abuse.ch, IP командных серверов ботнетов), обновлять по таймеру:

curl -s https://feodotracker.abuse.ch/downloads/ipblocklist.txt \
  | grep -vE '^#|^$' \
  | while read ip; do nft add element inet threat c2ips "{ $ip }"; done

Рядом того же класса: ThreatFox (IOC от abuse.ch и Spamhaus, почасовое обновление) и для майнинг-пулов доменный CoinBlockerLists.

Вывод по malware: два слоя обязательны. Доменный security.dat в routing ноды закрывает C&C и майнеры, ходящие по имени. IP-фид на nftables добивает тех, кто ходит по голому адресу. Порознь каждый оставляет дыру ровно того размера, что закрывает другой.

Идея «почтовые домены в DIRECT»: честная оценка

Замысел такой: домены gmail.com, mail.ru, smtp.* отправлять напрямую с домашнего IP клиента, а не через ноду. Разберём честно, потому что тут легко переоценить эффект.

Сначала — где ставить правило, иначе оно не сделает то, что задумано:

  • В серверном конфиге ноды direct всё равно уходит с IP ноды, просто без второго прыжка. С IP клиента это не отправит: сервер физически не может отразить пакет через аплинк клиента.
  • Увести трафик на IP клиента можно только правилом в клиентском профиле подписки. Тогда локальный Xray на устройстве матчит домен до входа в туннель и шлёт напрямую.
{
  "type": "field",
  "domain": ["domain:gmail.com", "domain:mail.ru", "domain:yandex.ru",
             "domain:outlook.com", "regexp:^smtp\\.", "regexp:^mail\\."],
  "outboundTag": "direct"
}

Обязательно с "outbound":{"tag":"direct","protocol":"freedom","settings":{"domainStrategy":"UseIP"}} — иначе резолв уйдёт мимо DNS-модуля Xray, это наша старая грабля с прямым freedom.

Что это даёт. Легитимный пользователь, чей почтовый клиент ходит на smtp.gmail.com по имени, уводится из туннеля на свой IP. Меньше почтового трафика через ноду.

Чего это НЕ даёт, и это главное. Спам-бот соединяется по IP на порт 25 — у такого соединения нет доменного имени, правило по домену его не видит. Восстановить домен из сырого SMTP Xray не умеет: sniffing понимает только http, tls, quic, но не SMTP. То есть против прямого IP-флуда на 25 идея с DIRECT-доменами не работает вообще никак. А даже если бот резолвит домен — это MX домена его жертвы, случайный у каждого письма, а не gmail.com из вашего списка.

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

Увидеть абуз до того, как прилетит бан

Чёрные списки — это уже последствие. Заметить абуз раньше можно по всплеску исходящих соединений:

# сколько исходящих на почтовые порты прямо сейчас
conntrack -L -p tcp 2>/dev/null | grep -E 'dport=(25|465|587)' | wc -l

# топ портов назначения — ловит и майнинг на нестандартных портах
conntrack -L -p tcp 2>/dev/null | grep -oE 'dport=[0-9]+' | sort | uniq -c | sort -rn | head -20

Автоматизируется тем же приёмом, что и остальной мониторинг: systemd-таймер раз в минуту считает соединения на 25/465/587 и типовые майнинг-порты, при превышении порога шлёт алерт и при желании рвёт текущие сессии через conntrack -D -p tcp --dport 25.

⚠️ Привязать «много исходящих на 25» к конкретному клиенту на уровне ядра нельзя. Все клиенты выходят через один процесс Xray с одним IP, и conntrack не знает, чей это UUID. Чтобы найти виновника, нужен access-лог Xray с полем email у каждого клиента в инбаунде: включаете лог, парсите строки с to tcp:...:25, агрегируете по email. Это единственный способ связать исходящее соединение с аккаунтом — фаервол хоста тут слеп.

Проверить репутацию и сняться из списков

Проверка одного IP сразу по многим спискам:

# Spamhaus ZEN (SBL+XBL+PBL одним запросом) для IP 2.3.4.5 — октеты в обратном порядке
dig +short 5.4.3.2.zen.spamhaus.org

Плюс веб: check.spamhaus.org, mxtoolbox.com/blacklists.aspx (сверяет по сотне DNSBL сразу), barracudacentral.org.

Порядок делистинга важен: сначала устраните причину, потом подавайте заявку. Список снимет адрес и через час залистит обратно, если абуз ещё идёт.

  • Spamhaus CBL — автоматически, но откажет, если источник ещё активен.
  • Spamhaus SBL — заявку шлёт владелец IP через ссылку на странице листинга, снятие 24–72 часа.
  • Barracuda — форма removal-request, до 48 часов после устранения причины.
  • UCEPROTECT — снимается сам примерно через 7 дней без спама.

Профилактика: чистый IP с самого начала

На бюджетных хостерах адреса выдают из общего пула без чистки истории. Новый арендатор наследует репутацию предыдущего, который мог спамить или майнить. Проверяйте IP через check.spamhaus.org до того, как заведёте на него клиентов. Грязный — просите замену, а не тратьте недели на делистинг чужого прошлого. Как вообще оценивать репутацию адреса, разобрано в «Чистые IP».

Провайдер как второй барьер

Многие хостеры блокируют 25 порт сами: DigitalOcean режет 25/465/587 у новых серверов, AWS глушит 25 навсегда. Для VPN это хорошо — лишний барьер поверх вашего. Но полагаться только на него нельзя: Hetzner и OVH 25 обычно не трогают, submission-порты не блокирует почти никто, а абузер может выпросить у поддержки разблокировку. Свой egress-фильтр нужен независимо от хостера.

И держите настроенным abuse-контакт у хостера. Hetzner и OVH дают от 6 до 48 часов на реакцию по жалобе, а при игноре не делистят IP, а сносят сервер. Первый же необработанный репорт может стоить вам не адреса, а всей ноды.

Чек-лист

  • [ ] Исходящий порт 25 закрыт на ноде (nftables + свой systemd-юнит, IPv6 тоже).
  • [ ] 465/587 закрыты или под общим лимитом.
  • [ ] security.dat в routing ноды: блок malware, phishing.
  • [ ] IP-фид C&C (Feodo/ThreatFox) на nftables, обновление по таймеру.
  • [ ] Мониторинг исходящих на 25/587 и майнинг-порты, алерт при всплеске.
  • [ ] Access-лог Xray с email, чтобы найти виновного клиента.
  • [ ] IP проверен в Spamhaus до заведения клиентов.
  • [ ] abuse-контакт у хостера настроен и кто-то его читает.

Смежное: защита от DDoS и флуда по ссылке, чек-лист безопасности сервиса, лимиты памяти и логов ноды.