Port Scanner — Custom Nmap Wrapper
This writeup documents the development of a custom Python-based Nmap wrapper tool built to streamline penetration testing workflows. The tool automates common Nmap scan profiles, parses XML output into structured reports, flags known vulnerable service versions, and exports results as HTML and CSV. It was built for personal use during CTF competitions and lab engagements to reduce repetitive manual scanning steps.
Project Info
- Type: Custom Security Tool — Python
- Language: Python 3
- Dependencies: python-nmap, argparse, Jinja2, csv
- Features: Scan profiles, XML parsing, vuln flagging, HTML/CSV export
Step 1 — Project Structure
The project was structured as a single CLI tool with modular functions for scanning, parsing, and reporting:
/nmap-wrapper
scanner.py -- main entry point
profiles.py -- predefined scan profiles
parser.py -- XML output parser
reporter.py -- HTML and CSV report generator
vulns.py -- known vulnerable version database
templates/
report.html -- Jinja2 HTML report template
requirements.txt
Keeping parsing and reporting separate from the core scanner makes it easy to add new output formats or scan profiles without touching the main logic.
Step 2 — Scan Profiles
Common scan types were defined as named profiles so the user does not need to remember Nmap flags for each scenario:
# profiles.py
PROFILES = {
"quick": "-T4 -F --open",
"full": "-T4 -p- --open --min-rate 3000",
"stealth": "-sS -T2 -p- --open",
"version": "-sV -sC -p- --open -T4",
"udp": "-sU -T4 --top-ports 200",
"vuln": "-sV --script vuln -T4"
}
Profiles are selected at runtime via the --profile
argument — removing the need to remember flag combinations
for every different scan scenario.
Step 3 — Core Scanner
The scanner uses the python-nmap library to execute
scans programmatically and capture structured output:
# scanner.py
import nmap
import argparse
from profiles import PROFILES
from parser import parse_results
from reporter import generate_report
def run_scan(target, profile, output):
nm = nmap.PortScanner()
flags = PROFILES.get(profile, PROFILES["version"])
print(f"[*] Scanning {target} with profile: {profile}")
print(f"[*] Nmap flags: {flags}")
nm.scan(hosts=target, arguments=flags)
results = parse_results(nm)
generate_report(results, output)
return results
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-t", "--target", required=True)
parser.add_argument("-p", "--profile", default="version")
parser.add_argument("-o", "--output", default="report")
args = parser.parse_args()
run_scan(args.target, args.profile, args.output)
Running the tool from the command line:
# Quick scan
python3 scanner.py -t 10.10.10.40 -p quick
# Full version scan with report output
python3 scanner.py -t 10.10.10.0/24 \
-p version -o lab_scan
# Vulnerability script scan
python3 scanner.py -t 192.168.1.10 -p vuln
Step 4 — XML Parsing
The parser extracts host, port, service, version, and state data from the python-nmap result object into a clean structured dictionary for downstream processing:
# parser.py
def parse_results(nm):
hosts = []
for host in nm.all_hosts():
host_data = {
"ip": host,
"hostname": nm[host].hostname(),
"state": nm[host].state(),
"ports": []
}
for proto in nm[host].all_protocols():
for port in nm[host][proto].keys():
svc = nm[host][proto][port]
host_data["ports"].append({
"port": port,
"proto": proto,
"state": svc["state"],
"service": svc["name"],
"version": svc.get("version",""),
"product": svc.get("product",""),
"vuln": check_vuln(svc)
})
hosts.append(host_data)
return hosts
Step 5 — Vulnerability Flagging
A lightweight local database of known vulnerable service versions is checked against each discovered service. Matches are flagged directly in the scan results:
# vulns.py
KNOWN_VULNS = {
"vsftpd 2.3.4": "CVE-2011-2523 — Backdoor RCE",
"ProFTPD 1.3.3c": "CVE-2010-4221 — Remote Buffer Overflow",
"Samba 3.0.20": "CVE-2007-2447 — Username Map RCE",
"OpenSSH 7.2p2": "CVE-2016-6515 — DoS / Auth Bypass",
"Apache 2.4.49": "CVE-2021-41773 — Path Traversal RCE",
"Apache 2.4.50": "CVE-2021-42013 — Path Traversal RCE",
"IIS 6.0": "CVE-2017-7269 — Buffer Overflow RCE",
"Elastix 2.2.0": "CVE-2012-4869 — LFI / RCE"
}
def check_vuln(svc):
key = f"{svc.get('product','')} \
{svc.get('version','')}".strip()
for vuln_key, cve in KNOWN_VULNS.items():
if vuln_key.lower() in key.lower():
return cve
return None
Flagged vulnerabilities are highlighted in red in the
HTML report and marked with a VULN column
in the CSV export for quick triage.
Step 6 — HTML and CSV Report Generation
Scan results are exported as a formatted HTML report using Jinja2 templates and a flat CSV for import into spreadsheets or vulnerability trackers:
# reporter.py
from jinja2 import Environment, FileSystemLoader
import csv, datetime
def generate_report(results, output):
# HTML report
env = Environment(loader=FileSystemLoader("templates"))
tmpl = env.get_template("report.html")
html = tmpl.render(
results=results,
generated=datetime.datetime.now()
)
with open(f"{output}.html", "w") as f:
f.write(html)
# CSV export
with open(f"{output}.csv","w",newline="") as f:
w = csv.writer(f)
w.writerow(["IP","Port","Proto",
"Service","Version","Vuln"])
for host in results:
for p in host["ports"]:
w.writerow([
host["ip"], p["port"],
p["proto"], p["service"],
p["version"], p["vuln"] or ""
])
The HTML report uses colour coding — open ports in green, vulnerable services highlighted in red, and a summary table at the top showing total hosts, open ports, and vulnerability count at a glance.
Step 7 — Subnet and Range Support
The tool supports single IPs, CIDR ranges, and hyphenated ranges — passed directly to Nmap:
# Single host
python3 scanner.py -t 10.10.10.40 -p version
# CIDR subnet scan
python3 scanner.py -t 192.168.1.0/24 -p quick
# Hyphenated range
python3 scanner.py -t 10.10.10.1-50 -p full
# Multiple targets from file
python3 scanner.py -t targets.txt -p version
Target file support reads one IP or range per line and passes them to Nmap as a space-separated list — allowing large scope engagements to be managed from a single target file prepared during scoping.
Step 8 — Installation and Usage
The tool is installable in any Python 3 environment with a single pip command:
# Clone the repository
git clone https://github.com/user/nmap-wrapper
cd nmap-wrapper
# Install dependencies
pip3 install -r requirements.txt
# requirements.txt
python-nmap==0.7.1
Jinja2==3.1.2
argparse
# Verify Nmap is installed
nmap --version
# Run your first scan
python3 scanner.py -t 10.10.10.3 -p version -o lame_scan
# Output: lame_scan.html and lame_scan.csv
Key Takeaways
- Wrapping Nmap in Python removes the need to remember flag combinations for every scan type
- XML parsing with python-nmap gives structured data — far more useful than grepping raw output
- A local vulnerable version database adds instant triage value without relying on online lookups
- Jinja2 templates make HTML reports easy to customise for different clients or engagements
- Separating scan profiles, parsing, and reporting keeps the codebase clean and extensible
- CSV export allows scan results to be imported into vulnerability trackers like Jira or Notion
Tools Used
Python 3— core language for the wrapper toolpython-nmap— Nmap Python bindings for programmatic scanningNmap— underlying scan engineJinja2— HTML report templatingargparse— CLI argument parsingcsv— flat file export for spreadsheet importVS Code— development environment