Tools · Nmap and network discovery · 18 / 25
Turn Nmap XML into a CSV asset inventory with Python
Parse Nmap XML with the Python standard library to build a host, port, and service spreadsheet that owners can review.
Owners read spreadsheets, not scan logs
A scan finding is only useful when the person who owns the host can see it. Most service owners will not read a terminal dump, but they will review a sorted table of address, hostname, port, service, and version. Building that table from XML means it is reproducible and does not depend on copy-paste.
Python’s xml.etree.ElementTree is enough. No third-party packages are needed, which keeps the script easy to run on a locked-down analyst machine.
Lab: the script
Save the script below as nmap2csv.py and run it against an XML file from your lab scan. It walks every host element, skips hosts that were not up, and writes one row per open port. Product and version come from the service element that -sV fills in.
Open the CSV in a spreadsheet and sort by service. Clusters jump out immediately: every host running an old web server version, or every machine with RDP open.
import csv, sys
import xml.etree.ElementTree as ET
tree = ET.parse(sys.argv[1])
writer = csv.writer(sys.stdout)
writer.writerow(["ip", "hostname", "port", "proto", "service", "product", "version"])
for host in tree.getroot().iter("host"):
if host.find("status").get("state") != "up":
continue
ip = host.find("address[@addrtype='ipv4']")
ip = ip.get("addr") if ip is not None else ""
name = host.find("hostnames/hostname")
name = name.get("name") if name is not None else ""
for port in host.iter("port"):
if port.find("state").get("state") != "open":
continue
svc = port.find("service")
get = (lambda k: svc.get(k, "")) if svc is not None else (lambda k: "")
writer.writerow([ip, name, port.get("portid"), port.get("protocol"),
get("name"), get("product"), get("version")])
# python3 nmap2csv.py lab.xml > inventory.csvAdd the columns that drive action
Add an owner column by joining on hostname or address with an export from your CMDB or DHCP server. Add a “first seen” column by keeping last week’s CSV and marking rows that are new. Those two fields turn a list into a work queue.
Keep the scan date and the Nmap command in the file’s first rows or filename. A reader should never have to guess how stale the table is.
Handle it as sensitive data
An inventory of open services is a map an attacker would love. Share it through access-controlled storage, not email attachments, and trim it to the hosts each owner is responsible for.
If you have an existing vulnerability platform, import the XML there instead. The script is for small teams and labs where no platform exists yet.
Diagram
From scan to owner-ready table
- Scan
Run with -sV -oA so XML holds service details.
- Parse
Walk host and port elements with ElementTree.
python3 nmap2csv.py lab.xml > inventory.csv
- Enrich
Join owners and mark rows new since last week.
- Share
Give each owner their slice through controlled storage.
Check yourself
1. Which XML element holds product and version from -sV?
2. Why does the script check host status before reading ports?
3. Which added column most directly turns the table into a work queue?