Відкритий і забутий порт — класичний шлях усередину сервера: база даних на публічній адресі, налагоджувальна панель, контейнер, що опублікував порт, хоча ніхто цього не хотів. Перевірка займає хвилину, якщо знати дві команди й одну пастку. Усе, що нижче, ми виконали на свіжому сервері 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.
Чек-лист після кожної зміни
sudo ss -tulpn: кожен рядок з0.0.0.0або[::]— це те, що ви справді хотіли відкрити?nmap ip-сервераз ноутбука: чи збігаються відкриті порти?- База даних, кеш чи адмін-панель на публічній адресі: сьогодні ж перенесіть на
127.0.0.1або за файрвол. - Кожен запис
ports:у Docker: чи починається він з127.0.0.1:, якщо це не публічний вебпорт? - SSH відкритий усім: додайте fail2ban або вхід лише за ключем.
На Windows Server та сама перевірка — Get-NetTCPConnection -State Listen, а правила живуть у брандмауері Windows. У кожного Linux-тарифу, починаючи з Nano за $3, є версія з власною IPv4-адресою, і там те, що ви бачите в ss, бачить і весь світ — за винятком вашого файрвола.
Коментарі
Поки немає коментарів. Будьте першим.