Автоматическая инвентаризация сети: nmap и список хостов в CSV

Управлять активами, которых нет в списке, невозможно: неизвестный сервер не попадёт в план обновлений и не будет проверен сканером уязвимостей. Ручной список быстро устаревает, поэтому инвентаризацию стоит повторять автоматически. Самый доступный инструмент для сети — nmap: он находит отвечающие хосты и определяет, какая служба слушает на каждом открытом порту. Ниже скрипт и результаты проверки на тестовой сети с тремя узлами.
Статья опирается на логику направления «Управление ИТ-активами» из методики зрелости Группы Сбер (источник); это методика для крупных компаний, мы используем её как ориентир и пересказываем своими словами. Проверка выполнена на изолированной тестовой сети Docker, после неё всё удалено.
Что должно получиться
| Уровень | Что это значит на практике |
|---|---|
| 1. Начальный | Есть актуальный список серверов и служб в сети, он обновляется автоматически, а не по памяти ответственного |
Критерии сформулированы нами по мотивам методики, это ориентир, а не оценка соответствия.
Скрипт инвентаризации
Скрипт сначала находит отвечающие хосты в подсети (nmap -sn, без сканирования портов), затем для каждого хоста определяет открытые порты и службы и собирает результат в CSV:
#!/usr/bin/env bash
# Инвентаризация сети: какие хосты и службы отвечают в указанной подсети. Только обнаружение, без эксплуатации.
# Запускать с правом на сканирование этой сети (согласие владельца инфраструктуры).
set -euo pipefail
NET="${1:?укажите подсеть, например 10.77.0.0/24}"
# top-1000 nmap + типовые порты инфраструктуры, которые в топ-1000 не входят (базы, кэши, админки)
EXTRA_PORTS="3306,5432,6379,9200,11211,27017,5984,2181,9092,8443,8080,9090,3000"
OUT="inventory-$(date +%Y%m%d-%H%M).csv"
echo "ip,hostname,port,service,product,version" > "$OUT"
nmap -sn "$NET" -oG - | awk '/Up$/{print $2}' > /tmp/hosts.$$
while read -r ip; do
nmap -sV -T4 --open -p "1-1000,$EXTRA_PORTS" "$ip" -oX - 2>/dev/null | python3 -c "
import sys, xml.etree.ElementTree as ET
root = ET.fromstring(sys.stdin.read())
for host in root.findall('host'):
ip = host.find('address').get('addr')
names = host.find('hostnames')
name = names.find('hostname').get('name') if names is not None and names.find('hostname') is not None else ''
for p in host.findall('.//port'):
state = p.find('state').get('state')
if state != 'open':
continue
svc = p.find('service')
service = svc.get('name') if svc is not None else ''
product = svc.get('product','') if svc is not None else ''
version = svc.get('version','') if svc is not None else ''
print(f'{ip},{name},{p.get(\"portid\")},{service},{product},{version}')
" >> "$OUT"
done < /tmp/hosts.$$
rm -f /tmp/hosts.$$
echo "готово: $OUT, строк: $(($(wc -l < "$OUT") - 1))"
Запуск: ./inventory.sh 10.0.5.0/24. Сканирование сети требует права её владельца: даже обнаружение портов посторонней сети без согласия может расцениваться как инцидент на стороне получателя.
Результат проверки
Тестовая сеть из трёх контейнеров: веб-сервер, Redis и PostgreSQL. Первый запуск с портами по умолчанию (топ-1000 портов nmap) дал только два хоста из трёх:
| IP | Порт | Служба | Версия |
|---|---|---|---|
| 10.88.0.10 | 80 | http | nginx 1.25.5 |
| 10.88.0.30 | 5432 | postgresql | PostgreSQL |
Redis на порту 6379 в список не попал, хотя порт был открыт (проверка отдельным сканированием -p 6379 и прямым запросом к службе). Причина — в nmap порты для сканирования по умолчанию выбираются по частоте встречаемости в его собственной базе, и 6379 в неё по частоте не попадает даже при --top-ports 2000. Полное сканирование всех 65535 портов (-p-) порт находит, но на крупной сети это ощутимо медленнее. Поэтому в скрипте топ-1000 портов явно дополнен списком типовых портов баз данных, кэшей и админок (3306, 5432, 6379, 9200, 11211, 27017, 5984, 2181, 9092 и другие). После этого Redis и PostgreSQL попали в список оба:
ip,hostname,port,service,product,version
10.88.0.10,inv-web.inv-net,80,http,nginx,1.25.5
10.88.0.20,inv-redis.inv-net,6379,redis,Redis key-value store,7.4.11
10.88.0.30,inv-db.inv-net,5432,postgresql,PostgreSQL DB,9.6.0 or later
Вывод: список портов по умолчанию в любом сканере рассчитан на типичный интернет-хост, а не на внутреннюю инфраструктуру. Для инвентаризации собственной сети список портов нужно дополнять вручную под используемые системы.
Что делать с результатом
- Сравнивайте с предыдущим запуском: новый IP или порт, которого не было вчера, — повод разобраться, легитимно это или нет.
- Сверяйте с учётной системой: если хост отвечает, но не внесён в реестр активов, у него нет ответственного и плана обновлений.
- Передавайте список в сканер уязвимостей: проверка нужна именно по отвечающим хостам и портам, а не по списку «что мы помним».
- Отмечайте незнакомые службы отдельно: неожиданный порт на известном хосте стоит проверить до внесения в реестр.
Порядок внедрения
- Определите подсети, которые нужно инвентаризировать, и получите согласие владельцев инфраструктуры.
- Дополните список портов под используемые в компании системы (базы, оркестраторы, админ-панели).
- Запускайте по расписанию (еженедельно для рабочих сетей, чаще для периметра) и храните историю результатов, а не только последний снимок.
- Сверяйте с реестром активов и заводите запись на каждый новый хост.
- Настройте уведомление о новых IP или портах, которых не было в прошлом запуске.
- Передайте результат в процесс управления уязвимостями: от скана до закрытия.
Диагностика
| Симптом | Причина и что делать |
|---|---|
| Известная служба не попадает в список | Её порт не входит в топ-1000 nmap. Добавьте порт в список явно или используйте -p- для полной проверки |
Хост не отвечает на -sn, но работает |
ICMP может быть заблокирован файрволом. Используйте -Pn, чтобы считать хост доступным без предварительного пинга, и проверьте точечно известные порты |
| Сканирование долгое на крупной сети | Увеличьте параллелизм (-T4, уже включён) и сканируйте подсети частями или по расписанию ночью |
| Результат отличается от запуска к запуску | Нормально для динамических адресов (DHCP, контейнеры). Сверяйте по MAC-адресу или имени хоста, а не только по IP |
| Версия службы определена неверно | Определение версии (-sV) работает по сигнатурам и может ошибаться, особенно на нестандартных портах. Используйте как ориентир, не как точный факт |
Ограничения
Сканирование видит только то, что отвечает по сети: выключенные хосты, устройства за NAT без прямой видимости и узлы с полностью закрытым файрволом в список не попадут. Это не заменяет учёт активов через управление конфигурациями и агенты на хостах, а дополняет его независимой проверкой «что реально отвечает в сети сейчас».
Где это в модели зрелости
Материал относится к направлению «Управление ИТ-активами» (материалы направления, карта зрелости). Начальный уровень требует хотя бы актуального списка активов: автоматическая инвентаризация сети даёт такой список без постоянной ручной работы и служит входом для управления уязвимостями и учёта активов.
Как проверить
- Инвентаризация запускается по расписанию, а не только когда кто-то вспомнил.
- Список портов дополнен под используемые в компании системы, а не ограничен значениями по умолчанию.
- Новые хосты и порты, которых не было в прошлом запуске, попадают в уведомление.
- Каждый отвечающий хост сверен с реестром активов и имеет ответственного.
Источники
- Методика оценки уровня зрелости кибербезопасности компании, Группа Сбер: sberbank.ru.
- Документация nmap: nmap.org.






















