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-порт; усе інше недоступне за задумом.

Коментарі

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

Залишити коментар

Коментарі проходять модерацію перед публікацією.