Исходящие соединения на незнакомые IP: как поймать и что это значит
Большинство админов пристально следят за входящими подключениями — кто ломится по SSH, кто сканирует порты — и почти не смотрят, куда сервер сам стучится наружу. А зря: если сервер скомпрометирован, вредоносный процесс почти всегда должен с кем-то связаться снаружи — за командами, за выгрузкой данных или чтобы отчитаться в ботнет. Легитимный сервер делает предсказуемый, ограниченный набор исходящих подключений, и любое новое направление в этом наборе — сигнал, который стоит проверить раньше, чем он превратится в инцидент.
Содержание
Какой исходящий трафик нормален для сервера
Прежде чем ловить аномалии, нужно понимать, что такое норма. У типового сервера (веб-бэкенд, API, воркер) исходящие соединения обычно укладываются в короткий список категорий:
- DNS — запросы к резолверам, обычно на 53 порт: либо к
127.0.0.53(systemd-resolved локально), либо напрямую к резолверам провайдера или к публичным (например, из/etc/resolv.conf). - NTP — синхронизация времени, порт 123, к пулу серверов из
/etc/chrony.confили/etc/ntp.conf. - Пакетные менеджеры — обращения к репозиториям дистрибутива при
apt update/dnf update, обычно нерегулярные, по расписанию unattended-upgrades. - Известные API и сервисы приложения — платёжный шлюз, объектное хранилище, почтовый релей (25/465/587), сторонний SMS/push-сервис, внешняя БД — всё, что явно прописано в конфигах приложения.
- Мониторинг и логирование — агент, отправляющий метрики или логи на внешний коллектор (Grafana Cloud, Loki, Sentry и т.п.).
- Деплой и системные обновления — git-репозиторий, Docker registry, CDN с образами.
Ключевая мысль: этот список должен быть у вас не в голове, а на бумаге (или в конфиге). Если на вопрос «куда стучится этот сервер» вы не можете ответить без пятиминутного ss, значит, у вас нет базовой линии для сравнения — а без неё аномалию поймать почти невозможно, вы просто не заметите новый IP среди привычного шума.
Смотрим текущие соединения: ss -tupn
Основной инструмент — ss (замена устаревающему netstat, читает данные напрямую из netlink, а не парсит /proc построчно, поэтому быстрее на серверах с большим числом соединений):
sudo ss -tupn state established
Разбор флагов: -t — TCP, -u — UDP, -p — показать процесс (PID/имя, требует root), -n — не резолвить имена (иначе вывод тормозит на DNS для каждого адреса). state established отфильтровывает только активные соединения, убирая LISTEN-сокеты, которые тут не интересны.
Типичная строка вывода:
tcp ESTAB 0 0 10.20.0.5:443 203.0.113.44:51322 users:(("nginx",pid=1122,fd=14))
Читается так: локальный процесс nginx (PID 1122) держит соединение с удалённым адресом 203.0.113.44 — если сервер работает как веб-бэкенд, это, скорее всего, входящее по природе (клиент подключился к вашему 443), просто ss показывает оба направления в общей таблице. Чтобы отделить именно исходящие, смотрите на то, какой порт — локальный или удалённый — выглядит «серверным» (443, 22, 25) против эфемерного (обычно выше 32768): если удалённый порт эфемерный, а локальный — известный сервис, соединение входящее; и наоборот.
Полезные сужения:
# Только исходящие к конкретному порту
sudo ss -tupn state established '( dport = :443 )'
# Уникальные удалённые IP среди установленных соединений
sudo ss -tupn state established | awk '{print $6}' | grep -oE '^[0-9.]+' | sort -u
# Наблюдать в реальном времени
watch -n2 "sudo ss -tupn state established"
Дополнить картину по процессам помогает lsof:
sudo lsof -i -P -n | grep ESTABLISHED
Он даёт то же самое с другого ракурса и иногда подсвечивает процессы, которые ss -p не смог сопоставить (например, если процесс уже завершается или доступ к его namespace ограничен).
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверСтроим базовую линию и сверяем с ней
Разовый просмотр ss ловит только то, что происходит прямо сейчас — короткоживущее соединение (например, однократный запрос к API раз в час) вы просто не увидите в моменте. Поэтому нужна база для сравнения, а не только снимок.
Практический способ — собирать снимки соединений по расписанию и сравнивать с эталонным списком:
#!/bin/bash
# /usr/local/bin/snapshot-conns.sh
sudo ss -tupn state established | awk '{print $6}' | grep -oE '^[0-9.]+' | sort -u \
>> /var/log/conn-snapshots/$(date +%F).txt
sort -u /var/log/conn-snapshots/$(date +%F).txt -o /var/log/conn-snapshots/$(date +%F).txt
Запускайте его через cron каждые 5-10 минут, а раз в сутки сравнивайте накопленный список уникальных IP с файлом allowlist.txt, который вы поддерживаете вручную:
comm -23 /var/log/conn-snapshots/$(date +%F).txt /etc/security/allowlist.txt
comm -23 покажет строки, которые есть в снимке, но отсутствуют в allowlist — это и есть кандидаты на проверку. Для каждого нового IP полезно сразу прогнать:
whois 203.0.113.44 | grep -i orgname
dig -x 203.0.113.44 +short
Владелец подсети и обратная запись часто сразу объясняют происхождение (CDN, облачный провайдер, известный SaaS) — но не доверяйте им полностью: whois и PTR — это данные, а не доказательство, атакующая инфраструктура тоже размещается в облаках с обычными on-имёнами.
Отдельная сложность — сервисы за CDN и с ротацией IP (объектные хранилища, платёжные шлюзы): их адреса меняются, и allowlist по IP для них быстро устаревает. Для таких направлений практичнее фиксировать в базовой линии не конкретный IP, а связку «домен + порт + процесс», и полагаться на файрвол уровня приложения или на DNS-логирование, а не на статичный список адресов.
Исходящий файрвол: default deny с allowlist
Входящий файрвол с default deny — привычная практика (см. чек-лист безопасности нового сервера), а вот исходящий чаще оставляют полностью открытым — «зачем ограничивать сервер в том, куда ему обращаться». Это ровно та логика, которую эксплуатирует C2-канал: раз исходящее не фильтруется, вредоносный процесс может стучаться куда угодно без препятствий. Симметричный принцип «запрещено всё, что не разрешено явно» на OUTPUT-цепочке резко сужает атакующему пространство для манёвра — тот же провал, что описан в антипаттерне «файрвол — разрешить всё», только зеркально для исходящего трафика.
Пример на nft (nftables) — базовая структура с default drop на выходе:
table inet filter {
chain output {
type filter hook output priority 0; policy drop;
# loopback и уже установленные/связанные соединения
oif "lo" accept
ct state established,related accept
# DNS к локальному резолверу
ip daddr 127.0.0.53 udp dport 53 accept
ip daddr 127.0.0.53 tcp dport 53 accept
# NTP
udp dport 123 accept
# HTTPS/HTTP наружу (компромисс — см. ниже)
tcp dport { 80, 443 } accept
# SMTP-релей на известный IP
ip daddr 203.0.113.10 tcp dport 587 accept
# всё остальное — лог и drop
log prefix "OUTPUT-DENY: " drop
}
}
Важный практический нюанс: жёстко ограничить исходящий 443 только конкретными IP почти нереально для сервера, который обращается к чему-то за CDN или к SaaS с динамическими адресами — список будет протухать быстрее, чем вы его обновляете. Рабочий компромисс — разрешить 80/443 широко (это всё равно закрывает нестандартные порты, на которых чаще всего сидят простые C2), а точечный контроль по конкретным направлениям делать через allowlist на уровне DNS (allow только резолвинг определённых доменов) или через прокси с логированием исходящих HTTP(S)-запросов, если задача требует строгого разбора.
Разворачивайте default deny поэтапно: сначала переведите правило drop в log-only режим на несколько дней, соберите, что реально стучится наружу, дополните allowlist, и только потом переключайте на drop. Правило без такого прогона почти гарантированно оборвёт что-то легитимное (обновления, вебхуки, деплой) в неожиданный момент — а на OUTPUT-цепочке это может сломать и сам механизм, которым вы правите файрвол удалённо, если используете что-то вроде туннеля для управления. Держите под рукой консольный доступ через панель провайдера, а не только SSH, на случай если ошибётесь в порядке правил.
Признаки, что соединение действительно подозрительное
Не любой незнакомый IP — компрометация: это может быть новый мирный сервис, о котором забыли упомянуть в документации, или ротация IP у существующего партнёра. Насторожить должно сочетание признаков, а не один факт:
- Нестандартный порт — не 80/443/53/123/587, а что-то вроде 4444, 8080 на IP, который не относится к известной инфраструктуре, или произвольный высокий порт на обеих сторонах.
- Регулярный интервал («биконинг») — соединения к одному адресу через равные промежутки времени (каждые 60 секунд, каждые 5 минут) — классический паттерн C2-опроса, в отличие от рваного трафика реальных пользовательских запросов.
- Объём данных не по профилю — сервер, который обычно только отдаёт контент, вдруг стабильно передаёт исходящий трафик заметного объёма на один и тот же адрес — возможный признак эксфильтрации.
- Процесс не соответствует ожиданиям — соединение держит не
nginx/postgres/известный сервис, а что-то с общим именем вродеsystemd-workerилиkworker, при этом бинарник лежит не там, где положено (/tmp,/var/tmp,/dev/shm):
sudo ls -l /proc/<PID>/exe
sudo cat /proc/<PID>/cmdline | tr '\0' ' '
- Массовые исходящие сканирования — если сервер вдруг открывает короткие соединения на 22/23/2323/445 к большому числу разных IP подряд — характерный признак участия в ботнете, который сканирует интернет в поисках новых жертв.
- DNS-запросы к доменам со случайными на вид именами — паттерн, характерный для DGA (алгоритмически генерируемых доменов), которым некоторые C2 подбирают адрес управляющего сервера.
Что делать при обнаружении подозрительного соединения
Первый порыв — тут же заблокировать IP и убить процесс. Не делайте этого сразу. Резкая блокировка:
- лишает вас возможности понять масштаб — что ещё затронуто, была ли закреплена персистентность (cron, systemd unit, SSH-ключ), уходили ли данные раньше;
- может насторожить атакующего (если управление автоматическое, потеря связи с C2 иногда триггерит заранее заложенный деструктивный сценарий — самоуничтожение, шифрование, чистку логов);
- убирает единственный шанс собрать доказательства для разбора инцидента или, если данные клиентов затронуты, для последующего отчёта.
Порядок действий:
- Зафиксируйте состояние соединения и процесса — не полагаясь на память, сохраните вывод прямо в файл с таймстампом:
date -u > /root/incident-evidence.txt
sudo ss -tupn state established >> /root/incident-evidence.txt
sudo lsof -i -P -n >> /root/incident-evidence.txt
- Снимите информацию о процессе, пока он жив: PID, путь к бинарнику, командную строку, владельца, время запуска, открытые файлы:
ps -fp <PID>
sudo ls -la /proc/<PID>/exe /proc/<PID>/cwd
sudo cat /proc/<PID>/environ | tr '\0' '\n'
- Снимите хэш подозрительного бинарника, если он ещё доступен на диске (может быть удалён после запуска, тогда берите копию из
/proc/<PID>/exe, пока процесс жив):
sudo sha256sum /proc/<PID>/exe
- Запишите трафик к этому адресу отдельно, чтобы было что разбирать офлайн:
sudo tcpdump -i any host 203.0.113.44 -w /root/suspect-203.0.113.44.pcap
- Проверьте персистентность — cron у всех пользователей, systemd-юниты, файлы автозагрузки,
~/.ssh/authorized_keysна предмет чужих ключей.
- Изолируйте, но не обязательно выключайте — если сервер продакшн, изоляцию часто делают на уровне сети (например, временное правило файрвола, режущее только этот адрес или помещающее хост в отдельный VLAN), сохраняя систему включённой для дальнейшего анализа, а не выдёргивая шнур, что теряет содержимое оперативной памяти.
Дальнейшие шаги — по чек-листу разбора инцидента, они не специфичны для сетевой части: пошаговый план описан в статье «Взломали сервер: пошаговый план», а как задокументировать сам разбор — в материале про написание разбора инцидента.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Нужен ли root для ss -tupn?
Для флага -p (показ процесса и PID) — да, без root вы увидите соединения, но не сможете определить, какой процесс их держит, что обесценивает большую часть анализа.
Как часто пересматривать базовую линию (allowlist)?
При каждом изменении состава сервисов на сервере — новый деплой, новая интеграция, смена почтового релея — и, как минимум, раз в месяц просто перечитывать список глазами: со временем туда попадает мусор (адреса сервисов, от которых давно отказались), и это тоже стоит вычищать.
CDN и облачные сервисы постоянно меняют IP — как их учитывать в allowlist?
По возможности не фиксируйте такие направления по конкретному адресу, а закладывайте в базовую линию сам факт «обращение к домену X на 443» и контролируйте на уровне DNS или прокси-логов, а не статичного списка IP — иначе будете либо получать постоянный шум ложных срабатываний, либо держать в allowlist огромные диапазоны, что обесценивает саму идею ограничения.
Если C2 маскируется под обычный HTTPS на 443 — как его вообще отличить от легитимного трафика?
По содержимому пакетов не отличите без расшифровки (а расшифровка транзитного TLS — отдельная и спорная практика). Ловится по метаданным: необычный объём для процесса, который не должен столько передавать, регулярность интервалов, репутация IP/ASN, к которому идёт соединение, и SNI/сертификат, если его логируете на файрволе или прокси.
Чем это отличается от IDS/IPS?
IDS/IPS (Suricata, Zeek и подобные) анализируют трафик по сигнатурам и эвристикам автоматически и в реальном времени — это следующий уровень зрелости. Подход из этой статьи — ручной и полуавтоматический контроль на основе ss и файрвола — минимальная база, которая работает без дополнительного софта и уже закрывает большую часть простых случаев компрометации.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →