Tools · Nmap and network discovery · 19 / 25
How to compare two Nmap scans with ndiff to spot new services
Run the same scan on a schedule, diff the XML with ndiff, and alert on hosts or ports that appeared since the last baseline.
Change is the signal
A single scan says what is open. Two scans a week apart say what changed, and change is what defenders care about: a new host on the server VLAN, a database port that appeared on a web server, or a service version that went backward. ndiff, which ships with Nmap, compares two XML files and prints only the differences.
For the comparison to mean anything, the scans must use the same targets, options, and vantage point. Keep the command in a script so it never drifts.
Reading ndiff output
Lines starting with a minus are from the old scan only, and lines with a plus are new. A host that appears under a plus with its ports is new. A port line changing from closed to open on an existing host is a new service. Version changes appear as a minus and plus pair on the same port.
ndiff --xml produces machine-readable differences, which is the format to feed into alerting.
ndiff baseline.xml today.xml ndiff --xml baseline.xml today.xml > changes.xml
Lab: a weekly diff job
Write a small shell script that scans the lab with fixed options, saves a dated XML file, and diffs it against the previous one. Run it, then start a new listener on a lab VM and run it again. The diff should show only that port.
Schedule it with cron or a systemd timer on a lab machine. Mail or post the output only when it is non-empty, so the job is quiet when nothing changes.
#!/usr/bin/env bash set -euo pipefail cd /srv/scans today=$(date +%F) sudo nmap -sV --top-ports 1000 -T4 -oX "lab-$today.xml" 192.168.56.0/24 >/dev/null prev=$(ls lab-*.xml | sort | tail -2 | head -1) if [ "$prev" != "lab-$today.xml" ]; then ndiff "$prev" "lab-$today.xml" > "diff-$today.txt" || true [ -s "diff-$today.txt" ] && cat "diff-$today.txt" fi
Triage a change
For every new host or port, ask three questions: does it match a change ticket, who owns it, and should it be reachable from where the scan ran. If any answer is unknown, it becomes a ticket.
Treat disappearances seriously too. A monitoring agent port that vanished might mean a host was decommissioned, or that someone disabled the agent.
Diagram
What ndiff prints for one change
A new port on an existing host between two weekly scans.
ndiff baseline.xml today.xml
-Nmap 7.95 scan initiated Mon Oct 26 as: nmap -sV --top-ports 1000 ... +Nmap 7.95 scan initiated Mon Nov 02 as: nmap -sV --top-ports 1000 ...
192.168.56.101: PORT STATE SERVICE VERSION -5432/tcp closed postgresql +5432/tcp open postgresql PostgreSQL DB 16.4
- The host existed in both scans.
- Port 5432 changed from closed to open.
- Check for a change ticket and confirm it should be reachable.
Check yourself
1. In ndiff output, what does a line beginning with + mean?
2. Why must both scans use identical options?
3. A monitoring agent port disappeared from a server. What is the right reaction?