MAATRIX / Блог / Неизвестный открытый порт на бою: чей он и можно ли его закрыть

Неизвестный открытый порт на бою: чей он и можно ли его закрыть

MAATRIX

Смотрите ss -tlnp на боевом сервере и видите строчку: порт 47821, слушает какой-то процесс, никто из команды не может сказать, что это и зачем. Гуглить название порта бесполезно — это явно не что-то из справочника IANA, а что-то локальное. Первая реакция — тут же закрыть правилом firewall от греха подальше. Вторая, более здравая — сначала понять, что это, а уже потом решать, можно ли трогать: за нестандартным портом может стоять как забытый тестовый сервис, так и часть продакшена, о которой просто не знает конкретно вы.

Когда нужна эта методика, а когда — что-то другое

Это узкая задача: у вас один конкретный порт, происхождение которого непонятно, и нужно решить его судьбу, не сломав ничего рабочего. Если вместо одного порта вы получили сервер целиком без документации и хотите понять картину в общем — процессы, сайты, базы, cron — это отдельная, более широкая задача, разобранная в статье «Как понять, что вообще крутится на чужом сервере: карта за вечер». Там порты — лишь одна из пяти категорий, которые нужно проинвентаризировать за один проход. Здесь же ситуация другая: карта, скорее всего, уже есть или не нужна, а есть один непонятный порт, требующий прицельного разбора.

Главное правило до начала разбора: не убивайте процесс и не удаляйте/не создавайте правило firewall в первые минуты, каким бы соблазн ни был. Если это часть чужой продакшен-логики, резкое вмешательство оборвёт что-то работающее прямо сейчас — и разбираться придётся уже в режиме инцидента, а не спокойного расследования.

Шаг 1. Найти процесс, который реально слушает порт

Первая задача — превратить голое число в конкретный процесс с PID, именем и путём к бинарнику.

ss -tlnp | grep :47821

Вывод даёт PID и имя процесса, например users:(("node",pid=18422,fd=11)). Если прав не хватает и ss не показывает имя, тот же результат для конкретного порта даёт:

sudo lsof -i :47821 -sTCP:LISTEN

lsof сразу показывает и пользователя, от имени которого запущен процесс, — сервис от root и сервис от случайного локального пользователя воспринимаются по-разному.

Дальше стоит выжать из /proc/PID максимум, прежде чем что-либо трогать:

PID=18422

tr '\0' ' ' < /proc/$PID/cmdline; echo          # полная командная строка
sudo readlink -f /proc/$PID/exe                  # реальный путь к бинарнику
sudo readlink -f /proc/$PID/cwd                   # рабочий каталог процесса
sudo cat /proc/$PID/environ | tr '\0' '\n'         # переменные окружения
ps -o pid,ppid,user,lstart,etime,cmd -p $PID       # когда стартовал, сколько живёт

readlink -f /proc/$PID/exe — не формальность: если ссылка заканчивается на (deleted), бинарник на диске уже удалили или подменили после запуска, а процесс продолжает работать со старой версией в памяти. Так бывает и после безобидного апдейта пакета без перезапуска сервиса, и как способ спрятать вредоносный бинарник от файлового сканирования — разница проясняется на следующих шагах, но сам факт (deleted) — повод присмотреться внимательнее.

lstart даёт точное время запуска — сверьте его с историей деплоев и логом обновления пакетов (/var/log/dpkg.log или /var/log/apt/history.log). Процесс, стартовавший ровно в момент последнего деплоя, почти наверняка часть приложения. Процесс, который живёт третий месяц без перезапуска и ни к чему известному не привязан, — уже отдельный вопрос.

Если процесс живёт в Docker-контейнере, PID в ss/lsof будет в пространстве имён хоста, но быстрее посмотреть на сам контейнер:

docker ps --format '{{.ID}}\t{{.Names}}\t{{.Ports}}' | grep 47821
docker inspect <container_id> --format '{{.Config.Image}}  {{.Config.Cmd}}'

Это часто объясняет происхождение порта быстрее, чем копание в процессах хоста.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Шаг 2. Понять происхождение: пакет, деплой или что-то без роду и племени

Зная путь к бинарнику, проверьте, откуда он взялся в системе:

# Debian/Ubuntu
dpkg -S /usr/bin/имя_бинарника
# RHEL/AlmaLinux/CentOS
rpm -qf /usr/bin/имя_бинарника

Если команда находит пакет — это управляемый софт, установленный штатно, и вопрос смещается с «что это» на «зачем он слушает именно наружу». Если ответ «no path found» — бинарник не принадлежит ни одному пакету, то есть попал на диск иначе: вручную, через pip/npm/go install, деплой-скриптом приложения, либо, в худшем случае, кем-то посторонним. Само по себе это не приговор — собственный код проекта тоже не пакет, — но сужает круг версий.

Проверьте, зарегистрирован ли процесс как управляемый сервис:

systemctl status имя-сервиса
systemctl show имя-сервиса -p FragmentPath,ExecStart,Environment

FragmentPath покажет физический путь к unit-файлу — если это недавно созданный файл в /etc/systemd/system/, велика вероятность, что сервис поставил кто-то из команды при развёртывании, и проще спросить коллег напрямую, чем гадать по логам.

Если процесс не сервис и не контейнер, а просто висит дочерним от шелл-сессии, screen, tmux или nohup — это сильный маркер «кто-то запустил руками и не убрал»:

ps auxf | grep -B3 $PID

Отдельно проверьте автозапуск, даже если процесс сейчас выглядит разовым:

crontab -l -u имя_пользователя
systemctl list-unit-files | grep enabled | grep -i имя_процесса

Если есть доступ к истории команд на сервере или к системе управления конфигурацией (Ansible, Terraform, деплой-скрипты в репозитории), поиск имени порта или процесса там часто закрывает вопрос за пару минут — быстрее, чем реконструкция по системным следам.

Тревожные признаки: когда пора подозревать не забытый сервис, а бэкдор

Большинство непонятных портов на практике — забытый тестовый инструмент, а не что-то злонамеренное. Но стоит явно проверить признаки, отличающие «забыли выключить» от «кто-то тут закрепился»:

  • Бинарник лежит в нетипичном месте/tmp, /dev/shm, /var/tmp, скрытый каталог в домашней директории. Легитимный софт туда не устанавливают надолго.
  • Имя маскируется под системное, но путь не сходится — процесс называется вроде [kworker/0:1] или systemd-udevd, но readlink -f /proc/PID/exe ведёт не в /usr/lib/systemd или ядро, а куда-то в домашний каталог пользователя.
  • Процесс виден в одном источнике и не виден в другом. Если ss -tlnp показывает PID, а ps -p PID отвечает «no such process» — сильный признак руткита, который патчит один из механизмов получения списка процессов, но не все сразу. Это уже отдельное, куда более серьёзное расследование.
  • Регулярные исходящие соединения на нетипичные адреса, помимо самого слушающего сокета:
  sudo lsof -p $PID -a -i

Короткие периодические подключения на внешний IP, не связанный ни с CDN, ни с апстримом приложения, — характерное поведение command-and-control канала.

  • Хеш бинарника не совпадает с ожидаемым. Для файла из пакета сверьте sha256sum с официальными данными; для файла без известного происхождения посчитайте хеш и, если политика безопасности организации это позволяет, проверьте его через публичные базы репутации файлов.

Если сходится два-три признака из списка — не пытайтесь закрыть порт своими силами и продолжать работу как ни в чём не бывало. Это уже вероятная компрометация, и дальнейшие шаги — изоляция, сбор улик до вмешательства, смена доступов — подробно разобраны в статье «Сервер взломали через забытый порт: реконструкция по логам». Не убивайте процесс до того, как снят снимок состояния системы: поспешное убийство процесса злоумышленника часто и есть момент, когда он замечает, что его нашли.

Шаг 3. Прежде чем что-либо закрывать — посмотреть, кто реально подключается

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

Активные соединения прямо сейчас:

ss -tn state established '( sport = :47821 or dport = :47821 )'

Для истории за время, а не моментального снимка — короткий захват трафика с ограничением по времени и размеру, чтобы не забить диск:

sudo timeout 3600 tcpdump -i any port 47821 -w /root/port_47821_capture.pcap

Через час tcpdump -r /root/port_47821_capture.pcap -nn | less покажет исходные IP и характер трафика: одиночные обращения раз в день похожи на health-check внешнего мониторинга, плотный постоянный поток — на то, что реально используется прикладным кодом. Если conntrack уже установлен, историю соединений можно посмотреть и без отдельного захвата: sudo conntrack -L | grep 47821.

Если за разумный срок наблюдения (несколько дней — неделя, в зависимости от критичности сервера) не нашлось ни одного подключения, кроме, возможно, вашего собственного при проверке, — это весомый аргумент в пользу закрытия. Если подключения регулярные и от знакомых источников (внутренние IP компании, другой ваш сервер) — сначала выясните у владельцев этих IP, что они делают на этом порту.

Шаг 4. Закрыть безопасно: временное правило вместо постоянного

Главный принцип — закрытие должно быть легко откатываемым. Не убирайте процесс из автозапуска, не удаляйте бинарник и не пишите правило firewall «навсегда» в первый же день — закрывайте временно, наблюдайте, и только потом, если ничего не сломалось, фиксируйте решение как постоянное.

Вариант 1 — временное правило ufw с меткой для памяти:

sudo ufw insert 1 deny 47821 comment 'temp block: unknown port, review 2026-09-15'
sudo ufw status numbered

comment тут не декорация: через неделю, когда правило всплывёт в ufw status numbered, не придётся вспоминать, зачем оно там. Откат — одна команда: sudo ufw delete deny 47821.

Вариант 2 — правило с автоматическим сроком жизни через at, если не хочется полагаться на память:

sudo ufw insert 1 deny 47821 comment 'temp block: unknown port'
echo "ufw delete deny 47821" | at now + 7 days

Через неделю правило снимется само — если к этому моменту решение уже принято осознанно, повторное удаление отсутствующего правила просто не навредит.

Вариант 3 — nftables со встроенным таймаутом, если используется он, а не ufw: в заранее объявленный set с флагом timeout можно добавить элемент, который сам исчезнет:

sudo nft add element inet filter blocked_ports { 47821 timeout 1h }

Удобно для быстрой проверки гипотезы «а что сломается, если порт временно недоступен», без риска забыть откатить руками.

Заблокировать доступ снаружи — не то же самое, что остановить сам сервис: процесс продолжит слушать порт локально, просто внешний трафик до него не дойдёт. Это полезное промежуточное состояние: если процесс кому-то нужен только на самом сервере, достаточно ограничить firewall и не трогать сервис. Если после проверки выяснилось, что процесс не нужен вообще, тогда уже:

sudo systemctl stop имя-сервиса
sudo systemctl disable имя-сервиса

Полное удаление бинарника, конфигов и данных — отдельный, ещё более осторожный шаг, который стоит делать не раньше, чем после дополнительного периода наблюдения за системой уже без остановленного сервиса.

Если период наблюдения прошёл, ничего не сломалось, а происхождение порта прояснилось (или подтверждена его ненужность) — переносите временное правило в постоянную конфигурацию firewall и обязательно фиксируйте находку в документации сервера: что это был за порт, кто его открыл (если удалось выяснить), почему закрыт или почему оставлен намеренно. Без такой записи ровно та же ситуация повторится для следующего человека, который наткнётся на этот порт через полгода.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Порт открыт, но ss/lsof вообще не показывают процесс — как так?

Возможно, сокет открыт из другого сетевого namespace — типичная ситуация с контейнерами: на хосте виден порт, проброшенный через NAT, а сам процесс живёт внутри контейнера. Проверьте docker ps на проброшенные порты. Если и там ничего нет — само по себе отсутствие видимого процесса тревожный признак, стоит перечитать раздел про руткиты выше.

Процесс легитимен, но слушает 0.0.0.0 вместо только локального интерфейса — это та же задача?

Нет, более простая: происхождение известно, дело только в конфигурации. Правьте bind-адрес в конфиге сервиса на 127.0.0.1 и перезапускайте его, а firewall используйте лишь как страховку на время правки. Общий разбор такой настройки — в статье «Открытые порты на сервере: как проверить и закрыть».

Порт принадлежит Docker-контейнеру, а ufw deny на него как будто не действует — почему?

Docker пишет собственные правила в iptables в обход ufw, и обычный deny часто не срабатывает, потому что цепочка DOCKER обрабатывается раньше пользовательских правил. В этом случае надёжнее ограничить публикацию порта самим Docker (-p 127.0.0.1:PORT:PORT вместо -p PORT:PORT) либо явно добавлять правило в цепочку DOCKER-USER.

Сколько по-хорошему нужно наблюдать перед окончательным закрытием?

Ориентир — от нескольких дней для сервисов с ежедневной активностью до одной-двух недель, если ожидается редкое обращение (сверка, резервное задание, интеграция раз в отчётный период). Для критичного сервера, где цена ошибки высока, лучше продлить наблюдение, чем закрыть слишком рано.

Владелец порта нашёлся, но сам не может объяснить, зачем порт открыт наружу, а не только локально?

Обычная ситуация с сервисами, которые по умолчанию биндятся на все интерфейсы (многие СУБД и брокеры сообщений). Сузьте bind-адрес до 127.0.0.1 или доверенной приватной сети, если внешний доступ не нужен, и только после этого убирайте временное firewall-правило — оно станет избыточным, но не вредным.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →