N E T H R A N. W E D A G E

Loading

Cybersecurity undergraduate and web developer based in Sri Lanka, passionate about penetration testing, VAPT, and ethical hacking.

Home / Portfolio / Python / Port Scanner — Custom Nmap Wrapper
cat ~/python/python-port-scanner.md
Python

Port Scanner — Custom Nmap Wrapper

05 Mar 2024 Linux GitHub Intermediate

Python script that wraps Nmap for automated recon — service version detection, OS fingerprinting, and outputs a formatted HTML/JSON report.

Python Nmap Recon Automation

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 tool
  • python-nmap — Nmap Python bindings for programmatic scanning
  • Nmap — underlying scan engine
  • Jinja2 — HTML report templating
  • argparse — CLI argument parsing
  • csv — flat file export for spreadsheet import
  • VS Code — development environment
Project Info
Category Python
Difficulty Intermediate
OS / Target Linux
Points GitHub
Date 05 Mar 2024
Tools Used
Python 3 python-nmap Jinja2