Список открытых портов — это и есть список способов до сервера добраться. Всё остальное — фаерволы, WAF, IDS — надстройки над ним. Поэтому проверка «что у меня слушает» стоит первой в любом аудите, и она же чаще всего даёт неприятный результат: половина найденного открыта не вами, а установщиком какого-нибудь пакета.
Читаем вывод
sudo ss -tulpn
Ключи: t — TCP, u — UDP, l — только слушающие, p — процесс, n — не преобразовывать номера в имена. Без sudo колонка с процессом будет пустой, и смысл теряется.
Смотреть надо на колонку Local Address:Port, и разница там принципиальная:
127.0.0.1:3306— служба доступна только с самой машины. Это хорошо;0.0.0.0:3306— со всех адресов IPv4, то есть из интернета. Это то, что нужно проверять;[::]:3306— то же самое для IPv6. Отдельная строка, и её регулярно не замечают;203.0.113.25:443— на конкретном адресе, обычно осмысленно.
Дальше по каждой строке с 0.0.0.0 или [::] задаём один вопрос: должен ли посторонний человек иметь возможность сюда обратиться. Для 80 и 443 ответ «да». Почти для всего остального — «нет».
Обычные находки
Redis, порт 6379. Самая опасная строка из возможных. По умолчанию Redis не требует пароля, а его команды позволяют записать файл на диск — то есть чужой ключ в authorized_keys. Между появлением Redis на публичном адресе и его использованием проходят часы, иногда меньше. Проверьте bind 127.0.0.1 и protected-mode yes в конфиге.
Memcached, 11211/UDP. Даже если внутри ничего ценного, ваш сервер станет усилителем чужих атак — и претензии придут от хостера.
MySQL и PostgreSQL, 3306 и 5432. Пароль есть, но перебор по нему идёт непрерывно, а версии СУБД обновляются реже, чем хотелось бы. Наружу они не нужны почти никогда: приложение живёт на той же машине, а вам для работы хватит SSH-туннеля.
Elasticsearch 9200, MongoDB 27017. Исторически без аутентификации по умолчанию. Публичные базы этих служб — постоянный источник новостей об утечках.
Docker API, 2375. Открытый порт управления Docker — это root на хосте без каких-либо паролей. Обычно появляется после экспериментов с удалённым доступом к докеру.
Панель управления и phpMyAdmin на своих портах: 8080, 8083, 10000. Не то чтобы их нельзя открывать, но именно они собирают на себя основной поток попыток.
Чинить на уровне службы, а не фаервола
Соблазн закрыть всё найденное правилом UFW понятен, но это второй рубеж, а не первый. Правило можно удалить по ошибке, фаервол — временно выключить при отладке, а Docker и вовсе публикует порты в обход UFW. Настройка привязки в конфиге службы переживает всё это:
- MySQL/MariaDB —
bind-address = 127.0.0.1; - PostgreSQL —
listen_addresses = 'localhost'; - Redis —
bind 127.0.0.1 ::1; - Docker Compose — публикация как
"127.0.0.1:5432:5432".
Фаервол ставится сверху как страховка, а не вместо этого.
Найти процесс, если непонятно
ss показывает имя и pid. Дальше:
sudo systemctl status <pid>
sudo lsof -i :8080
Первая команда назовёт юнит systemd, которому принадлежит процесс, — обычно этого достаточно, чтобы понять, что это и нужно ли оно. Незнакомый процесс, слушающий высокий порт и запущенный не из системного каталога — например, из /tmp или /dev/shm, — это уже не вопрос настройки, а повод для отдельного разбирательства.
Проверка снаружи обязательна
ss отвечает на вопрос «что слушает», но не на вопрос «до чего можно достучаться». Между ними стоят фаервол, NAT и правила хостера. Единственный честный ответ даёт сканирование с другой машины:
nmap -Pn -p- 203.0.113.25
nmap -Pn -p- -6 2001:db8::1
Второй командой не пренебрегайте: адрес IPv6 есть почти у каждого VPS, правила для него пишутся отдельно, а служба слушает обе версии протокола сразу.
Смотреть надо не разово
Список открытых портов меняется сам. Установили пакет — он привёл службу и открыл порт. Обновили панель — она вернула настройку по умолчанию. Запустили контейнер — он опубликовал порт мимо фаервола. Разовая проверка отвечает за сегодняшний день и ни за что больше.
Ценность здесь не в самом списке, а в его изменениях: новый порт, которого вчера не было, — короткий и очень информативный сигнал. Именно так это и стоит смотреть — снимком с историей, а не разбором вывода ss по памяти. Страница демо ниже показывает ровно это.