Network Scanning with Nmap
Nmap ("Network Mapper") is the most widely used network scanner in the world. Defenders use it to check what their own networks expose; testers use it to map an authorised target; auditors use it to prove that firewall rules really work. It is free, open source and installed on Kali.
In this lesson you will scan your own lab network with Nmap: find live hosts, find open ports, identify services and versions, run safe scripts, and save the results. Then you will parse Nmap's XML output with Python and compare two scans to spot new open ports, which is one of the most useful defensive habits you can build.
Where you may scan
Port scanning a network you do not own, without permission, can break the law and the provider's terms, and CERT-In lists "targeted scanning/probing" as a reportable incident. You have two legal targets:
- Your lab:
192.168.56.0/24, with the target VM at192.168.56.101. scanme.nmap.org: the Nmap Project gives everyone permission to scan this one host with Nmap. The permission does not cover exploit testing or denial-of-service attacks, and the project asks you not to run more than about a dozen scans against it per day.
All examples below use the lab. Run Nmap with sudo on Kali so it can use raw packets.
Step 1: find live hosts
A ping scan (-sn) checks which addresses respond, without scanning ports:
sudo nmap -sn 192.168.56.0/24On a local network Nmap uses ARP requests, which are very reliable. If a host blocks discovery probes but you know it exists, -Pn tells Nmap to skip discovery and scan it anyway.
Step 2: scan ports
sudo nmap 192.168.56.101 # top 1000 TCP ports, SYN scan
sudo nmap -p 22,80,443,3000,4280 192.168.56.101
sudo nmap -p- 192.168.56.101 # all 65535 TCP ports
sudo nmap -sU --top-ports 20 192.168.56.101 # 20 most common UDP portsWith root privileges the default is a SYN scan (-sS): Nmap sends a SYN and reads the reply without completing the handshake. Without root it falls back to a connect scan (-sT), which completes the handshake using the operating system. UDP scans (-sU) are slow because closed and filtered UDP ports are hard to tell apart.
Nmap reports each port in one of these states:
| State | Meaning |
|---|---|
| open | A program accepts connections on this port |
| closed | Reachable, but nothing is listening (the host replied with RST) |
| filtered | No useful reply, usually because a firewall dropped the probe |
| unfiltered | Reachable, but Nmap cannot tell if it is open or closed (seen with -sA) |
| open|filtered | No reply to a UDP or similar probe: could be either |
Step 3: identify services and versions
Knowing that port 4280 is open is not enough; you need to know what runs there and which version, because versions map to known vulnerabilities.
sudo nmap -sV -sC -O -p 22,3000,4280 192.168.56.101 -oA lab-target-sVtalks to each open port to detect the service and version.-sCruns Nmap's default scripts (the same as--script default).-Oguesses the operating system from how its network stack behaves.-oA lab-targetsaves normal (.nmap), XML (.xml) and grepable (.gnmap) output at once.
-A is a shortcut that turns on -sV, -sC, -O and traceroute together. Example output from a lab target (your versions and details will differ):
Nmap scan report for 192.168.56.101 Host is up (0.00041s latency). PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0) 3000/tcp open http Node.js Express framework 4280/tcp open http Apache httpd 2.4.65 ((Debian)) |_http-title: Login :: Damn Vulnerable Web Application (DVWA) MAC Address: 08:00:27:4A:90:11 (Oracle VirtualBox virtual NIC) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Lines starting with | come from scripts. Here http-title printed the page title, which confirms DVWA runs on port 4280.
Nmap Scripting Engine (NSE)
NSE scripts extend Nmap. Each belongs to categories such as safe, default, discovery, version, vuln, intrusive, brute, dos and exploit. The category tells you how risky a script is. In your lab you can try:
sudo nmap -p 4280 --script http-title,http-headers 192.168.56.101
sudo nmap -p 443 --script ssl-enum-ciphers 192.168.56.101
sudo nmap -p 4280 --script "safe and http-*" 192.168.56.101ssl-enum-ciphers lists the TLS versions and cipher suites a server accepts, which is how auditors find servers still allowing old protocols. Scripts in the intrusive, brute, dos and exploit categories can crash services or lock accounts; on a real engagement use them only if the rules of engagement explicitly allow it.
Timing and politeness
-T0 (slowest) to -T5 (fastest) set timing templates; the default is -T3. -T4 is fine inside your lab. On a real network, agree the scanning speed and time window with the owner, and remember that every scan appears in their firewall and IDS logs.
Parsing Nmap XML with Python
XML output (-oX or -oA) is designed for programs. Python's built-in xml.etree.ElementTree can turn it into a report:
import xml.etree.ElementTree as ET
xml_text = """<nmaprun>
<host><status state="up"/><address addr="192.168.56.101" addrtype="ipv4"/>
<ports>
<port protocol="tcp" portid="22"><state state="open" reason="syn-ack"/><service name="ssh" product="OpenSSH" version="9.6p1"/></port>
<port protocol="tcp" portid="3000"><state state="open" reason="syn-ack"/><service name="http" product="Node.js Express framework"/></port>
<port protocol="tcp" portid="3306"><state state="filtered" reason="no-response"/><service name="mysql"/></port>
<port protocol="tcp" portid="4280"><state state="open" reason="syn-ack"/><service name="http" product="Apache httpd" version="2.4.65"/></port>
</ports></host>
</nmaprun>"""
root = ET.fromstring(xml_text)
for host in root.iter("host"):
ip = host.find("address").get("addr")
print("Host", ip)
for port in host.iter("port"):
state = port.find("state").get("state")
svc = port.find("service")
product = " ".join(filter(None, [svc.get("product"), svc.get("version")]))
label = port.get("portid") + "/" + port.get("protocol")
print(f' {label:<9} {state:<9} {svc.get("name"):<6} {product}'.rstrip())Host 192.168.56.101 22/tcp open ssh OpenSSH 9.6p1 3000/tcp open http Node.js Express framework 3306/tcp filtered mysql 4280/tcp open http Apache httpd 2.4.65
Defensive use: spotting new open ports
Defenders scan their own servers on a schedule and compare results with a known-good baseline. A port that appears overnight could be a forgotten test service, a misconfiguration or a backdoor.
baseline = {"192.168.56.101": {22, 3000, 4280}, "192.168.56.103": {3389}}
today = {"192.168.56.101": {22, 3000, 4280, 8080}, "192.168.56.103": set(), "192.168.56.110": {21, 23}}
for host in sorted(set(baseline) | set(today)):
before, now = baseline.get(host, set()), today.get(host, set())
if host not in baseline:
print(f"{host}: NEW HOST with ports {sorted(now)}")
continue
for port in sorted(now - before):
print(f"{host}: port {port} newly OPEN, investigate")
for port in sorted(before - now):
print(f"{host}: port {port} now closed, confirm this was planned")192.168.56.101: port 8080 newly OPEN, investigate 192.168.56.103: port 3389 now closed, confirm this was planned 192.168.56.110: NEW HOST with ports [21, 23]
A new host offering FTP and Telnet, both clear-text protocols, deserves attention straight away.
Common mistakes
- Scanning outside your scope, even "just one quick scan". Check every target against your authorisation first.
- Trusting only the default 1000 ports. Services often run on high ports; use
-p-when you need certainty. - Reading "filtered" as "safe". It means Nmap could not tell; the service may still be reachable from elsewhere.
- Forgetting UDP. DNS, SNMP and other UDP services are often missed.
- Not saving output. Use
-oAso you have evidence and can compare scans later.
Key points
- Scan only your lab or
scanme.nmap.org, which permits Nmap scans only, about a dozen per day at most. -snfinds live hosts; a SYN scan (-sS) is the default as root;-p-scans all ports;-sUscans UDP.- Port states include open, closed, filtered and open|filtered.
-sV,-sC,-O(or-A) identify services, run default scripts and guess the OS.- NSE categories show how risky a script is; avoid intrusive, brute, dos and exploit scripts unless explicitly allowed.
- Save XML output and compare scans with a baseline to catch unexpected changes.
Exercise
Modify the XML parser so that it prints only open ports and adds a warning (clear-text protocol) for the services ftp, telnet and plain http. Test it by adding a port 21 with service name ftp and state open to the sample XML.
Show solution
import xml.etree.ElementTree as ET
xml_text = """<nmaprun><host><address addr="192.168.56.101"/><ports>
<port protocol="tcp" portid="21"><state state="open"/><service name="ftp"/></port>
<port protocol="tcp" portid="22"><state state="open"/><service name="ssh"/></port>
<port protocol="tcp" portid="3306"><state state="filtered"/><service name="mysql"/></port>
<port protocol="tcp" portid="4280"><state state="open"/><service name="http"/></port>
</ports></host></nmaprun>"""
CLEAR_TEXT = {"ftp", "telnet", "http"}
for port in ET.fromstring(xml_text).iter("port"):
if port.find("state").get("state") != "open":
continue
name = port.find("service").get("name")
warning = " (clear-text protocol)" if name in CLEAR_TEXT else ""
print(port.get("portid"), name + warning)It prints ports 21, 22 and 4280; FTP and HTTP get the warning, and the filtered MySQL port is skipped.
Quick Quiz
1. What is the Nmap Project's permission for scanme.nmap.org?
The Nmap Project allows Nmap scans of that host only, within polite limits.
2. Which option scans all 65535 TCP ports?
-p- means every port from 1 to 65535.
3. A port is reported as "filtered". What does this usually mean?
Filtered means no useful reply came back, usually because of a firewall.
4. Why should defenders compare today's scan with a baseline?
Unexpected changes are often the first sign of a problem.
Learn Cyber Security with a mentor
Free lessons get you started. Our Ethical Hacking & Cyber Security training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
