Короткий ответ

Картина портов — три слоя: что слушает процесс (ss -tulnp), что пропускает хостовый firewall, что видно снаружи (nmap с hop в той же зоне, что атакующий). Сверьте с матрицей «порт / bind / source CIDR / зачем». Расхождение ss vs nmap — нормальный сигнал (bind localhost, UFW, SG облака, DNAT). Не сканируйте только 127.0.0.1. Не отключайте UFW «чтобы nmap совпал». Хост host.example (10.0.20.10), пользователь admin.

Симптомы и как отличить

Это процедура, не инцидент. Признаки, что её нет:

  • в wiki «открыто 22 и 443», на деле :9100 и :2375;
  • скан с самого сервера nmap localhost;
  • Docker порты не в матрице.

Если уже ясно, что лишнее торчит в WAN — закрытие: лишние порты. Если фильтр выключен: firewall.

Возможные причины расхождений

  1. Слушает 127.0.0.1, внешний nmap closed/filtered.
  2. Слушает 0.0.0.0, UFW drop → filtered с WAN, open с LAN.
  3. docker-proxy vs процесс в namespace.
  4. IPv4 закрыт, :::443 открыт.
  5. Security group режет до гостя.
  6. Hairpin/NAT, скан «публичного IP» с LAN идёт другим путём.

Диагностика

Работайте от admin с sudo. Сохраняйте вывод в каталог заявки с датой.

1. Слушатели на хосте

sudo ss -tulnp
sudo ss -tulnp | awk 'NR==1 || /LISTEN/'
ip -br addr

Колонка Local Address:Port — bind. *:22 / 0.0.0.0:22 / [::]:22 / 127.0.0.1:22 различайте.

Процессы:

sudo lsof -iTCP -sTCP:LISTEN -P -n

2. Firewall

sudo ufw status verbose
sudo nft list ruleset | grep -E 'dport|policy'

Не ufw disable.

3. Docker/kube

docker ps --format '{{.Names}}\t{{.Ports}}' 2>/dev/null
sudo ss -tlnp | grep docker-proxy

Сокет API: docker.sock.

4. Скан с hop

С машины в admin-сети 10.0.20.0/24:

nmap -Pn -sT -p 22,80,443,3306,5432,6379,8080,9100,2375 10.0.20.10

С hop «как интернет» (другая сеть/NAT), тот же список. Не сканируйте чужие диапазоны и не устраивайте -p- на проде в рабочий день без окна (нагрузка, IDS). Для полного списка сначала ss, потом точечный -p по факту слушателей.

IPv6:

nmap -6 -Pn -p 22,80,443 2001:db8::10

Подставьте реальный адрес хоста, не пример db8 если его нет.

UDP (DNS, NTP, VPN) — отдельно -sU, медленнее, с пониманием.

Решение (как собрать матрицу)

Таблица в заявке, колонки:

ПортПротоколBindПроцессКто клиент (CIDR)FW правилоНужно
22tcp0.0.0.0sshd10.0.20.0/24ufw allow from …да
3306tcp0.0.0.0mysqldWAN???Anywhereнет → чинить

Для каждой строки «нет» — задача на bind или UFW (соседние статьи), не «документировать как фичу».

SSH-ожидание: sshd. Итог hardening: baseline.

Повторяемость:

mkdir -p /home/admin/port-audit
sudo ss -tulnp > /home/admin/port-audit/ss-$(date +%F).txt

Diff со вчера — регрессия.

Как читать результаты nmap без самообмана. open на 22 с WAN при политике «только VPN» — дефект SG или UFW Anywhere. filtered на 3306 при ss 0.0.0.0:3306 — фильтр работает, bind всё ещё опасен при ошибке правила: оставьте в матрице статус «слушает все, FW режет» и запланируйте bind. closed на 80 — ничего не слушает, RST дошёл: либо нет сервиса, либо bind только localhost (тогда с WAN closed, с localhost open — два скана).

Порядок съёмки в инциденте «нас сканируют»: не включайте ufw disable, чтобы «увидеть настоящие порты». Настоящие порты даёт ss. Nmap с WAN показывает экспозицию. Оба файла в заявку.

UDP: ss -ulnp. DNS 53, NTP 123, WireGuard 51820. Nmap -sU без -p списка на проде не делайте в рабочее время. Для WG внешний open/filtered зависит от firewall; сам ss должен показать 0.0.0.0:51820 только если это VPN-шлюз, не app-сервер.

IPv4 vs IPv6 матрица — две строки или явная пометка dual-stack. Часто AAAA забывают в UFW при IPV6=yes.

Автоматизация: скрипт раз в неделю с hop (runner в другой сети) пишет nmap XML, diff с эталоном. Эталон = матрица, не «вчерашний скан» (дрейф узаконивает дыру).

Не путайте listening и established: ss -tp куча 443 ESTAB не значит, что вы слушаете 443 как сервер — это исходящие. Для матрицы только LISTEN.

Повторите скан после ufw reload и после деплоя: conntrack может кратко показывать иное. Для отчёта фиксируйте UTC-время и hop-IP, с которого снимали nmap.

Как проверить, что проблема устранена

Процедура успешна, если:

  1. Файл ss и два nmap (LAN/WAN) лежат в заявке.
  2. Матрица покрывает все LISTEN кроме localhost-only, помеченных явно.
  3. WAN-open ⊆ разрешённые в политике.
  4. Firewall active.
  5. Повтор через неделю даёт diff=0 или тикеты на дельту.

Это не «закрыть всё», а проверяемый инвентарь. Закрытие — отдельные работы.

Если не помогло

  • nmap с сервера ≠ с hop: используйте hop.
  • Cloud: снимите SG отдельно, приложите к матрице.
  • ss пустой для порта, nmap open: DNAT на другом узле.
  • conntrack/timeout: повтор с -Pn.
  • AppArmor не скрывает listen.

Профилактика

  • Матрица в git без секретов.
  • CI после деплоя: сравнить ss с allow-list.
  • Алерт на новый LISTEN.
  • Запрет docker -p без bind-адреса в шаблоне.

FAQ

Достаточно ли netstat?

ss штатный. netstat из net-tools может отсутствовать. Не ставьте пакет ради привычки, если ss есть.

nmap с Windows до Ubuntu

Нормально. Те же open/filtered. Не забудьте IPv6.

Сканировать UDP 1-65535

Дорого и шумно. Только по матрице и ss -ulnp.

localhost сервисы в матрице?

Да, пометка bind 127.0.0.1, клиент «только локально/ssh -L». Иначе забудете и опубликуете.

Можно ли masscan

Для периметра с окном — да, с rate limit. Для одного 10.0.20.10 хватит nmap -p по списку.