EQVPS
КабинетНачать

Как проверить открытые порты на VPS: что слушает сервер, что видит интернет и сюрприз от Docker

Посмотрите все порты, которые слушает сервер, найдите процесс за каждым, просканируйте сервер снаружи, как это сделал бы злоумышленник, и закройте лишнее. Разбираем порт Docker, который остаётся открытым даже при включённом файрволе. Все цифры сняты на свежем сервере с Ubuntu 24.04.

Открытый и забытый порт — классический путь внутрь сервера: база данных на публичном адресе, отладочная панель, контейнер, который опубликовал порт, хотя никто этого не хотел. Проверка занимает минуту, если знать две команды и одну ловушку. Всё, что ниже, мы выполнили на свежем сервере Ubuntu 24.04 из нашего образа и просканировали его со второй машины, так что выводы будут такими же, как на вашем сервере.

1. Что слушает сервер

Команда — ss. Буквы означают: TCP, UDP, слушающие сокеты, номера портов и имя процесса:

sudo ss -tulpn

На свежем сервере она печатает шесть строк:

Netid State  Local Address:Port  Process
udp   UNCONN    127.0.0.54:53    systemd-resolve
udp   UNCONN 127.0.0.53%lo:53    systemd-resolve
tcp   LISTEN    127.0.0.54:53    systemd-resolve
tcp   LISTEN 127.0.0.53%lo:53    systemd-resolve
tcp   LISTEN       0.0.0.0:22    sshd
tcp   LISTEN          [::]:22    sshd

Главный столбец — Local Address. Он говорит, кто может подключиться:

АдресКому доступен
127.0.0.1:… или 127.0.0.53Только программам на этом сервере
0.0.0.0:…Всем по IPv4, если не блокирует файрвол
[::]:…Всем по IPv6 (обычно и по IPv4)
10.0.0.5:… (конкретный адрес)Всем, кто может достучаться до этого адреса

То есть свежий сервер показывает наружу ровно одно: SSH на порту 22, по IPv4 и IPv6. DNS-резолвер на 127.0.0.53 — приватный.

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

sudo ss -tlnp | awk 'NR>1 && $4 !~ /^(127\.|\[::1\])/ {print $4, $6}'

Запускайте это после каждой установки. Мы поставили nginx, Docker и одно маленькое приложение на Python — список вырос с 2 строк до 10, включая docker-proxy на 0.0.0.0:8081 и сокет containerd на случайном локальном порту.

2. Какой процесс занял порт

Когда порт занят и нужно узнать кем:

sudo lsof -i :80 -P -n
sudo fuser 80/tcp
sudo ss -tlnp 'sport = :80'

lsof читается легче всего. Для nginx он показал один главный процесс от root и воркер от www-data, оба на *:80, для IPv4 и IPv6. fuser печатает только PID — удобно в скриптах. Если порт нужно освободить, останавливайте службу, а не убивайте PID, иначе systemd перезапустит её: sudo systemctl stop nginx.

3. Что видит интернет

ss показывает взгляд сервера. Взгляд интернета может отличаться: между ними стоит файрвол, или служба привязана к 127.0.0.1. Чтобы увидеть реальную картину, сканируйте сервер с другой машины — с ноутбука или второго сервера. Инструмент — nmap (apt install nmap, а для Windows и macOS установщик с nmap.org).

nmap your-server-ip
nmap -p- your-server-ip
nmap -p 22,80,443,3306 your-server-ip

Первая форма проверяет 1 000 самых частых портов, вторая — все 65 535, третья — список. На нашем свежем сервере до включения файрвола:

Not shown: 999 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
Nmap done: 1 IP address (1 host up) scanned in 6.83 seconds

Полное сканирование 65 535 портов заняло 8,9 секунды и нашло тот же единственный порт. Закрытые порты мгновенно отвечают сбросом соединения, поэтому сканировать их быстро.

Важны три состояния:

  • open: программа приняла соединение. Это ваша поверхность атаки.
  • closed: сервер ответил, что здесь ничего не слушает. Безопасно, но подтверждает, что сервер существует, и показывает, чего на нём нет.
  • filtered: ответа нет. Файрвол отбросил пакет. Сканер не знает, есть ли за ним служба.

Для одного порта без установки чего-либо: nc -zv ip-сервера 443 отвечает succeeded или refused, а на filtered-порту висит до таймаута. Для UDP добавьте к nmap -sU и готовьтесь ждать; на нашем сервере три частых UDP-порта — 53, 123 и 161 — были closed.

Замечание для NAT-тарифов: из интернета доступен только SSH-порт, который мы вам пробросили. Сканирование общего адреса покажет нашу границу, а не ваш сервер, и остальные порты недоступны, что бы на них ни слушало. На тарифе с собственным IPv4 каждый открытый порт — под вашим управлением.

4. Закрыть лишнее

Три способа — от лучшего к самому широкому.

Привязать к localhost. Если службой пользуются только программы на сервере, пусть она слушает 127.0.0.1. Мы запустили маленькое веб-приложение с --bind 127.0.0.1 8080: ss показал его на 127.0.0.1:8080, а nmap снаружи сообщил 8080/tcp closed. Для баз данных это правильное умолчание: Redis, MySQL и PostgreSQL на Ubuntu из коробки слушают localhost, и наши руководства по Redis и MySQL так это и оставляют.

Остановить службу. sudo systemctl disable --now имя убирает слушателя навсегда. Проверьте через ss, что он исчез.

Включить файрвол. Сначала разрешите SSH, затем то, что раздаёте, потом включайте:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw enable
sudo ufw status verbose

Сканирование снаружи изменилось полностью:

Not shown: 997 filtered tcp ports (no-response)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8081/tcp open  blackice-icecap
Nmap done: 1 IP address (1 host up) scanned in 10.18 seconds

Каждый неразрешённый порт сменил closed на filtered, а полное сканирование 65 535 портов стало занимать 110 секунд вместо 8,9. Больше времени на каждый порт для сканера, меньше информации о вашем сервере. Наше руководство по UFW рассказывает про ограничение частоты, доступ с одного IP и IPv6.

Но посмотрите на третью строку.

5. Сюрприз от Docker

Порт 8081 остался открытым при включённом файрволе и без правила, которое его разрешает. Этот порт принадлежит контейнеру, который мы запустили командой docker run -p 8081:80 nginx:alpine. Docker добавляет в файрвол ядра собственные правила, и они проверяются раньше правил ufw. Правило, которое он записал на нашем сервере:

-A DOCKER ! -i docker0 -p tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:80

Любой пакет на порт 8081 перенаправляется в контейнер до того, как ufw успевает сказать своё слово. Так делает каждый контейнер с -p или записью в ports:, и ss честно показывает это как docker-proxy на 0.0.0.0:8081.

Исправление — публиковать порт только на localhost:

docker rm -f web
docker run -d --name web -p 127.0.0.1:8081:80 nginx:alpine

В Compose напишите "127.0.0.1:8081:80" в ports:. После изменения ss показал 127.0.0.1:8081, в правиле Docker появилось -d 127.0.0.1/32, а сканирование снаружи сообщило 8081/tcp filtered. Поставьте впереди nginx как обратный прокси с HTTPS, чтобы открытыми на сервере были только порты 22, 80 и 443.

Чек-лист после каждого изменения

  1. sudo ss -tulpn: каждая строка с 0.0.0.0 или [::] — это то, что вы действительно хотели открыть?
  2. nmap ip-сервера с ноутбука: совпадают ли открытые порты?
  3. База данных, кеш или админ-панель на публичном адресе: сегодня же перенесите на 127.0.0.1 или за файрвол.
  4. Каждая запись ports: в Docker: начинается ли она с 127.0.0.1:, если это не публичный веб-порт?
  5. SSH открыт всем: добавьте fail2ban или вход только по ключу.

На Windows Server та же проверка — Get-NetTCPConnection -State Listen, а правила живут в брандмауэре Windows. У каждого Linux-тарифа, начиная с Nano за $3, есть версия с собственным IPv4-адресом, и там то, что вы видите в ss, видит и весь мир — за вычетом вашего файрвола.

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

Какие порты открыты на свежем VPS?

На нашем образе Ubuntu 24.04 — один: SSH на порту 22. Кроме него слушает только локальный DNS-резолвер на 127.0.0.53 и 127.0.0.54, до которого из интернета не добраться. Сканирование всех 65 535 TCP-портов с другой машины нашло ровно один открытый порт.

Чем closed отличается от filtered?

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

Почему ss показывает порт, а nmap говорит, что он closed?

Служба слушает только 127.0.0.1, петлевой адрес. Программы на самом сервере могут подключиться, снаружи — никто. В выводе ss смотрите столбец Local Address: 127.0.0.1:8080 — приватный, 0.0.0.0:8080 или [::]:8080 доступен из интернета, если разрешает файрвол.

Я включил ufw, почему порт Docker всё ещё открыт?

Docker пишет собственные правила файрвола, и они срабатывают раньше правил ufw. Контейнер, запущенный с -p 8081:80, на нашем сервере был доступен снаружи при включённом ufw без правила для 8081. Публикуйте порт на localhost: -p 127.0.0.1:8081:80, а наружу поставьте nginx или Caddy.

Как проверить порт со своего компьютера?

Для одного порта — nc -zv ip-сервера 443, для тысячи самых частых — установите nmap и выполните nmap ip-сервера. На NAT-тарифе из интернета отвечает только ваш проброшенный SSH-порт; всё остальное недоступно по замыслу.

Комментарии

Пока нет комментариев. Будьте первым.

Оставить комментарий

Комментарии проходят модерацию перед публикацией.