Pentest

Building an Automated Nmap Network Scanner with Python

📅 August 20, 2025 ⏱ 8 min read 👤 Sagar Bidari

Nmap is the first tool any network security professional learns. But running Nmap manually and reading raw output is slow. This project automates the entire workflow: scan a target range, parse the XML output, flag interesting findings, and generate a clean report — all in Python.

Legal reminder: Only run Nmap against systems you own or have explicit written permission to test. Running this against external targets without authorisation is illegal under the Australian Criminal Code Act 1995.

Why Automate Nmap?

In a real penetration test or vulnerability assessment, you might scan hundreds of hosts. Automating the scan and report means:

Prerequisites

The Scanner Script

import nmap
import json
import datetime

RISKY_PORTS = {
    21: "FTP - plaintext, often misconfigured",
    22: "SSH - check for weak credentials",
    23: "Telnet - plaintext, should not be open",
    80: "HTTP - check for missing HTTPS redirect",
    443: "HTTPS",
    445: "SMB - check for EternalBlue (MS17-010)",
    3389: "RDP - high-value target for brute force",
    3306: "MySQL - should not be internet-exposed",
    5900: "VNC - often no auth by default",
}

def scan_target(target, ports="1-1024"):
    nm = nmap.PortScanner()
    print(f"[*] Scanning {target} ...")
    nm.scan(hosts=target, ports=ports, arguments="-sV -O --open")
    results = []

    for host in nm.all_hosts():
        host_data = {
            "ip": host,
            "hostname": nm[host].hostname(),
            "state": nm[host].state(),
            "os_guess": "",
            "open_ports": [],
            "risk_flags": []
        }

        # OS detection
        if "osmatch" in nm[host] and nm[host]["osmatch"]:
            host_data["os_guess"] = nm[host]["osmatch"][0]["name"]

        # Port enumeration
        for proto in nm[host].all_protocols():
            for port in nm[host][proto]:
                svc = nm[host][proto][port]
                port_info = {
                    "port": port,
                    "protocol": proto,
                    "state": svc["state"],
                    "service": svc["name"],
                    "version": svc.get("version", "")
                }
                host_data["open_ports"].append(port_info)

                if port in RISKY_PORTS:
                    host_data["risk_flags"].append(
                        f"Port {port} ({RISKY_PORTS[port]})"
                    )

        results.append(host_data)

    return results

def generate_report(results, output_file="scan_report.json"):
    report = {
        "scan_date": datetime.datetime.utcnow().isoformat(),
        "hosts_scanned": len(results),
        "total_risk_flags": sum(len(h["risk_flags"]) for h in results),
        "hosts": results
    }
    with open(output_file, "w") as f:
        json.dump(report, f, indent=2)
    print(f"[+] Report saved to {output_file}")
    return report

if __name__ == "__main__":
    target = "192.168.1.0/24"  # Your lab network
    results = scan_target(target)
    report = generate_report(results)

    print(f"\n{'='*50}")
    print(f"Scan complete — {report['hosts_scanned']} hosts")
    print(f"Risk flags: {report['total_risk_flags']}")
    for host in report["hosts"]:
        if host["risk_flags"]:
            print(f"\n[!] {host['ip']} ({host['hostname']})")
            for flag in host["risk_flags"]:
                print(f"    - {flag}")

What the Output Looks Like

Running this against my VirtualBox lab network (192.168.56.0/24) produced output like:

[*] Scanning 192.168.56.0/24 ...
[+] Report saved to scan_report.json

==================================================
Scan complete — 4 hosts
Risk flags: 7

[!] 192.168.56.101 (metasploitable)
    - Port 21 (FTP - plaintext, often misconfigured)
    - Port 23 (Telnet - plaintext, should not be open)
    - Port 445 (SMB - check for EternalBlue)
    - Port 3306 (MySQL - should not be internet-exposed)

Extending the Scanner

Add HTML Report Output

Replace the JSON output with a Jinja2 template to generate a styled HTML report — much more impressive to show clients or include in a portfolio write-up.

CVSS Scoring Integration

Cross-reference open ports against a local CVE database (NVD provides daily XML dumps) and append known vulnerabilities to each finding automatically.

Scheduled Scanning

Wrap the scanner in a cron job (Linux) or Task Scheduler (Windows) to run nightly and email you a diff of changes — new open ports, new hosts, closed services.

Why This Project Matters for Interviews

This project demonstrates three things that entry-level cybersecurity employers want to see: Python scripting ability, understanding of network reconnaissance, and awareness of why certain ports are risky. The GitHub repo for this project has become one of the most-discussed items in my interviews.

Related Articles