⚠ Legal Notice: This tool is intended exclusively for authorised penetration testing and security research. Using it against systems you do not own or have explicit written permission to test is illegal. The authors accept no liability for misuse.
- Overview
- Architecture
- Project Structure
- Scan Pipeline
- Installation
- Quick Start
- CLI Reference
- Module Documentation
- CVE Pattern Database
- Report Format
- Running the Test Suite
- Extending NVS
- Performance Tuning
- Limitations & Known Issues
- Contributing
- License
NVS is a zero-dependency Python CLI that automates the first four phases of a network penetration test — host discovery (ICMP + TCP-ping), port enumeration (TCP connect + UDP), service fingerprinting (banner parsing for 15+ protocols), and CVE triage (pattern matching against a curated 40-entry vulnerability database) — and exports structured JSON reports with CVSS-weighted risk scores and prioritised remediation actions.
Built for security engineers who need a fast, auditable, air-gap-friendly alternative to stringing together nmap + grep + spreadsheets.
Targets → Live hosts → Open ports → Service versions → CVE matches → JSON report
| Capability | Implementation | Privilege |
|---|---|---|
| Host discovery | ICMP Echo Request (raw socket) | root / CAP_NET_RAW |
| Host discovery (fallback) | TCP connect to common ports | none |
| Port enumeration | TCP three-way handshake | none |
| UDP port scanning | Protocol-specific probes | none |
| Banner grabbing | recv() / TLS wrap | none |
| Service fingerprinting | Regex parsers per protocol | none |
| OS detection | python-nmap (-O) | root (for Nmap) |
| CVE pattern matching | Local JSON database | none |
| Reporting | Structured JSON | none |
Key design principles:
- Zero mandatory third-party dependencies — the core pipeline runs on the Python standard library;
python-nmapandrichare optional extras. - Thread-pool concurrency —
ThreadPoolExecutoris used for both host discovery and port scanning; pool sizes are tunable per scan. - Graceful degradation — raw-socket ICMP falls back to TCP-ping when unprivileged; Nmap phase is silently skipped when the binary is absent.
- Structured output — every scan produces machine-readable JSON suitable for import into vulnerability-management platforms.
┌─────────────────────────────────────────────────────────────────┐
│ CLI (cli.py) │
│ argparse → target resolution → pipeline orchestration │
└────────────────┬────────────────────────────────────────────────┘
│
┌─────────▼─────────┐
│ Phase 1 │ host_discovery.py
│ Host Discovery │ ICMP ping sweep (raw socket)
│ │ TCP-ping fallback (no root needed)
└─────────┬──────────┘
│ live_hosts[]
┌─────────▼──────────┐
│ Phase 2a │ port_scanner.py
│ Port Enumeration │ TCP connect scan (multi-threaded)
│ │ Optional UDP probes
└─────────┬──────────┘
│ open_ports[]
┌─────────▼──────────┐
│ Phase 2b │ service_detector.py
│ Service Detection │ Banner parsing per protocol
│ │ TLS handshake + cert extraction
└─────────┬──────────┘
│ (optional)
┌─────────▼──────────┐
│ Phase 3 │ nmap_scanner.py
│ Nmap Enrichment │ OS fingerprinting (-O)
│ (optional) │ Service version detection (-sV)
│ │ NSE script execution
└─────────┬──────────┘
│
┌─────────▼──────────┐
│ Phase 4 │ cve_mapper.py
│ CVE Mapping │ Port / service / banner / version
│ │ cross-referenced against JSON DB
└─────────┬──────────┘
│
┌─────────▼──────────┐
│ Output │ report_generator.py
│ JSON Report │ Executive summary + risk score
│ │ Per-host findings + remediation
└────────────────────┘
Shared utilities: utils.py
IP/CIDR validation · hostname resolution · port-range parsing
ANSI colours · progress bar · timestamps
nvs/
│
├── scanner/ # Main Python package
│ ├── __init__.py # Public API re-exports
│ ├── cli.py # CLI entry point (argparse + orchestration)
│ ├── utils.py # Shared helpers (IPs, ports, colours, progress)
│ ├── host_discovery.py # Phase 1: ICMP + TCP-ping host sweep
│ ├── port_scanner.py # Phase 2a: TCP connect + UDP scanning
│ ├── service_detector.py # Phase 2b: Banner parsing & fingerprinting
│ ├── nmap_scanner.py # Phase 3: python-nmap wrapper (optional)
│ ├── cve_mapper.py # Phase 4: CVE pattern matching engine
│ └── report_generator.py # JSON report builder
│
├── data/
│ └── cve_patterns.json # Bundled CVE database (40 patterns, v1.0.0)
│
├── tests/
│ ├── __init__.py
│ ├── test_host_discovery.py # ICMP checksum, packet structure, sweep logic
│ ├── test_port_scanner.py # PortResult, TCP scan, UDP scan, port parsing
│ └── test_cve_mapper.py # CVE loading, matching, filtering, report gen
│
├── reports/ # Runtime output directory (git-ignored)
│ └── .gitkeep
│
├── requirements.txt # Optional dependencies
├── setup.py # Package installer & entry-point definition
├── .gitignore
└── README.md
Goal: Identify which IPs in the target range are reachable before spending time port-scanning dead hosts.
ICMP Sweep (preferred, requires root)
- A raw
AF_INET / SOCK_RAW / IPPROTO_ICMPsocket is created. - An ICMP type-8 (Echo Request) packet is built with:
- Type=8, Code=0
- Identifier =
os.getpid() & 0xFFFF - Sequence = 1
- Payload = timestamp bytes
- Checksum = RFC 1071 Internet Checksum over header + payload
- The reply (type-0 Echo Reply) is captured; RTT is computed.
TCP-Ping Fallback (no root)
When PermissionError is raised (no CAP_NET_RAW), a TCP connect to a list of common ports (80, 443, 22, 445…) is tried. Any successful connect_ex() == 0 means the host is alive.
Concurrency: ThreadPoolExecutor(max_workers=discovery_threads) — default 50 workers, tunable via --discovery-threads.
TCP Connect Scan
For each port, socket.connect_ex((target, port)) is called with settimeout(timeout). Return codes:
| Return code | State |
|---|---|
0 |
open |
ECONNREFUSED (111) |
closed |
| timeout / other | filtered |
Only open ports are returned by default (verbose mode includes all states).
Banner Grabbing (optional, --no-banners to disable)
After a successful connect:
- Passive recv: for protocols that send data immediately (SSH, FTP, SMTP, POP3, IMAP).
- HTTP GET probe: for HTTP/HTTPS ports, a minimal
GET / HTTP/1.0request is sent. - TLS wrap: for TLS ports (443, 8443, 993…) a second connection wraps the socket with
ssl.create_default_context()(verification disabled for self-signed certs).
UDP Scanning (--udp)
Protocol-specific probes are sent to well-known UDP ports. State inference:
| Result | State |
|---|---|
| Response received | open |
ConnectionRefusedError (ICMP port-unreachable) |
closed |
| Timeout | open|filtered |
Concurrency: ThreadPoolExecutor(max_workers=threads) — default 100 workers, tunable via --threads.
Per-port regex parsers extract structured metadata from raw banners:
| Service | Parser | Extracted Fields |
|---|---|---|
| SSH | _parse_ssh() |
product (OpenSSH), version, OS hint |
| FTP | _parse_ftp() |
product (ProFTPD, vsftpd), version |
| SMTP | _parse_smtp() |
product (Postfix, Exim), version |
| HTTP | _parse_http() |
product, version, framework hint |
| POP3 | _parse_pop3() |
version string |
| IMAP | _parse_imap() |
version string |
| MySQL | _parse_mysql() |
version (from Protocol v10 packet) |
| Redis | _parse_redis() |
version string |
| RDP | _parse_rdp() |
static identification |
For TLS ports, ssl.SSLSocket.cipher() and getpeercert() extract:
- Negotiated TLS version (TLSv1.2, TLSv1.3)
- Cipher suite name
- Certificate Common Name and SAN list
- Certificate expiry date
Activated with --nmap. Requires both:
nmapbinary on$PATHpip install python-nmap
Default arguments: -sV -O --version-intensity 5
NVS only passes already-discovered open ports to Nmap, making the run significantly faster than a full Nmap sweep.
Merge strategy: Nmap data is overlaid on the existing host dict:
productandversionare backfilled only when currently blank or "unknown".os_guessandos_accuracyare set at the host level.- NSE script output is stored in each port's
extra.nmap_scriptsdict.
NSE scripts (--nmap-scripts "default,vuln"): execute Nmap's built-in vulnerability detection scripts. The vuln category is intrusive — use only on authorised targets.
Each open port is tested against every pattern in data/cve_patterns.json using four matching dimensions (OR-logic across dimensions):
| Dimension | Condition |
|---|---|
| Port match | Target port ∈ affected_ports AND protocol matches |
| Service keyword | service_keywords[i] substring of detected service name |
| Banner pattern | banner_patterns[i] substring of raw banner (lowercased) |
| Version range | Detected version ∈ [version_min, version_max] (semver comparison) |
Findings are sorted by severity: CRITICAL → HIGH → MEDIUM → LOW → INFO.
CVSS severity thresholds (v3 scale):
| CVSS Score | Severity |
|---|---|
| 9.0 – 10.0 | CRITICAL |
| 7.0 – 8.9 | HIGH |
| 4.0 – 6.9 | MEDIUM |
| 0.1 – 3.9 | LOW |
| 0.0 | INFO |
Use --min-cvss 7.0 to suppress noise during initial triage.
| Requirement | Version | Purpose |
|---|---|---|
| Python | ≥ 3.8 | Runtime |
| pip | any | Package management |
| nmap binary | ≥ 7.0 | Phase 3 (optional) |
| Root / sudo | — | ICMP ping, Nmap -O |
# 1. Clone the repository
git clone https://github.com/example/network-vuln-scanner.git
cd network-vuln-scanner
# 2. Create and activate a virtual environment (recommended)
python3 -m venv .venv
source .venv/bin/activate # Linux / macOS
# .venv\Scripts\activate.bat # Windows
# 3. Install the package (core only — no third-party deps)
pip install -e .
# 4. Optional: Nmap integration
pip install -e ".[nmap]"
# 5. Optional: full development install
pip install -e ".[all]"nvs --help
python -m pytest tests/ -v# Scan a single host (top 100 ports, no root required)
nvs -t 192.168.1.10
# Scan a /24 subnet and save JSON report
sudo nvs -t 192.168.1.0/24 -p top-100 -r reports/scan.json
# Full TCP port range + UDP + Nmap OS detection
sudo nvs -t 10.0.0.5 -p 1-65535 --udp --nmap -r full_scan.json
# Read targets from file, filter findings CVSS >= 7.0
nvs -f targets.txt -p 22,80,443,3389,8080 --min-cvss 7.0 -r report.json
# Skip host discovery (all targets assumed live)
nvs -t 10.0.0.100 -p top-100 --skip-discovery -v
# Use custom CVE database + run Nmap NSE scripts
sudo nvs -t 10.0.0.1 -p top-100 --nmap --nmap-scripts "default,vuln" \
--cve-db /opt/my_cve_db.json -r vuln_report.jsonusage: nvs [-h] (-t TARGET | -f FILE) [-p PORTS] [--udp] [--skip-discovery]
[--timeout S] [--threads N] [--discovery-threads N] [--no-banners]
[--nmap] [--nmap-args ARGS] [--nmap-scripts SCRIPTS]
[--no-cve] [--cve-db PATH] [--min-cvss SCORE]
[-r FILE] [--no-color] [-v] [-q]
| Flag | Description |
|---|---|
-t, --target TARGET |
Single IP, CIDR block (192.168.1.0/24), or hostname |
-f, --target-file FILE |
Newline-delimited file of IPs / CIDRs / hostnames (# comments OK) |
| Flag | Default | Description |
|---|---|---|
-p, --ports PORTS |
top-100 |
top-100 | 22,80,443 | 1-1024 | mixed |
--udp |
off | Probe common UDP ports in addition to TCP |
--skip-discovery |
off | Treat all targets as live; skip Phase 1 |
| Flag | Default | Description |
|---|---|---|
--timeout S |
1.0 |
Per-port connection timeout (seconds) |
--threads N |
100 |
TCP scan thread-pool size |
--discovery-threads N |
50 |
Host discovery thread-pool size |
--no-banners |
off | Skip banner grabbing (faster; less service info) |
| Flag | Default | Description |
|---|---|---|
--nmap |
off | Enable Phase 3 Nmap enrichment |
--nmap-args ARGS |
-sV -O --version-intensity 5 |
Raw Nmap argument string |
--nmap-scripts SCRIPTS |
— | NSE scripts (e.g. default,vuln). Implies --nmap. |
| Flag | Default | Description |
|---|---|---|
--no-cve |
off | Skip Phase 4 CVE pattern matching |
--cve-db PATH |
data/cve_patterns.json |
Custom CVE patterns JSON file |
--min-cvss SCORE |
0.0 |
Minimum CVSS score to report (0.0 = all) |
| Flag | Default | Description |
|---|---|---|
-r, --report FILE |
— | Write JSON report to FILE |
--no-color |
off | Disable ANSI colour codes |
-v, --verbose |
off | Show closed / filtered ports |
-q, --quiet |
off | Suppress progress; print findings only |
Shared helpers used across the pipeline.
from scanner.utils import (
validate_ip, # bool: is string a valid IPv4/v6?
validate_cidr, # bool: is string valid CIDR notation?
expand_cidr, # List[str]: CIDR → individual host IPs
resolve_hostname, # Optional[str]: forward DNS lookup
reverse_dns, # Optional[str]: PTR record lookup
parse_port_range, # List[int]: "22,80-90,top-100" → sorted int list
is_root, # bool: running as root / Administrator?
format_duration, # str: 3661.0 → "1h 1m 1s"
get_timestamp, # str: ISO-8601 current timestamp
Colors, # ANSI colour namespace; Colors.disable() for plain
ProgressBar, # In-place terminal progress bar
)from scanner.host_discovery import HostDiscovery, probe_host
# Probe a single host
result = probe_host("192.168.1.10", timeout=1.0)
# → {"ip": "192.168.1.10", "alive": True, "rtt_ms": 0.8,
# "method": "icmp", "hostname": "web.local", "discovered_at": "..."}
# Sweep a subnet
hd = HostDiscovery(max_workers=50, timeout=0.5, resolve=True)
live = hd.sweep(["192.168.1.1", "192.168.1.2", ...])
# or
live = hd.discover_from_cidr("192.168.1.0/24")from scanner.port_scanner import PortScanner, UDPScanner
# TCP connect scan
scanner = PortScanner(timeout=1.0, max_workers=100, grab_banners=True)
results = scanner.scan("10.0.0.1", ports=[22, 80, 443, 8080])
for r in results: # only open ports returned
print(r.port, r.service, r.banner, r.rtt_ms)
print(r.to_dict()) # serialisable dict
# UDP scan
udp = UDPScanner(timeout=2.0, retries=2)
res = udp.scan_port("10.0.0.1", 53)
print(res.state) # "open" | "closed" | "open|filtered"from scanner.service_detector import ServiceDetector
det = ServiceDetector()
meta = det.detect(port=22, banner="SSH-2.0-OpenSSH_8.9p1 Ubuntu", tls=False)
# → {"service": "ssh", "product": "OpenSSH", "version": "8.9p1",
# "os_hint": "Ubuntu", "version_string": "SSH-2.0-OpenSSH_8.9p1 Ubuntu"}
meta = det.detect(port=443, banner="HTTP/1.1 200 OK\r\nServer: nginx/1.22.1",
tls=True, target="10.0.0.1")
# → {"service": "https", "product": "nginx", "version": "1.22.1",
# "tls_info": {"tls_version": "TLSv1.3", "cipher_name": "...", ...}}from scanner.nmap_scanner import NmapScanner
nmap = NmapScanner()
if nmap.is_available():
raw = nmap.scan("10.0.0.1", ports=[22, 80, 443])
host = nmap.merge_results(existing_host_dict, raw)
# NSE script scanning
raw = nmap.scan_with_scripts("10.0.0.1", [80], scripts="http-headers,http-title")from scanner.cve_mapper import CVEMapper
mapper = CVEMapper(min_cvss=7.0) # bundled DB
mapper = CVEMapper(cve_db_path="/path/custom.json") # custom DB
vulns = mapper.map_host(host_dict)
# → [{"cve_id": "CVE-...", "severity": "CRITICAL", "cvss_score": 9.8,
# "affected_port": 445, "remediation": "...", ...}]
# Batch
all_vulns = mapper.map_multiple_hosts(hosts) # Dict[ip, List[vuln]]
# DB statistics
print(mapper.stats())
# → {"total_patterns": 40, "by_severity": {"CRITICAL": 22, "HIGH": 8, ...}}from scanner.report_generator import ReportGenerator
import json
gen = ReportGenerator()
report = gen.generate(hosts=enriched_hosts, scan_info=meta)
json.dump(report, open("report.json", "w"), indent=2)The bundled database (data/cve_patterns.json) ships with 40 patterns covering critical network-facing vulnerabilities and common misconfigurations.
| CVE ID | Name | Severity | Primary Port(s) |
|---|---|---|---|
| CVE-2017-0144 | EternalBlue (SMBv1 RCE) | CRITICAL | 445, 139 |
| CVE-2020-0796 | SMBGhost (SMBv3 RCE) | CRITICAL | 445 |
| CVE-2019-0708 | BlueKeep (RDP RCE) | CRITICAL | 3389 |
| CVE-2021-44228 | Log4Shell | CRITICAL | 80, 443, 8080 |
| CVE-2014-0160 | Heartbleed | HIGH | 443, 993, 995 |
| CVE-2014-6271 | Shellshock | CRITICAL | 80, 443 |
| CVE-2021-26855 | ProxyLogon (Exchange) | CRITICAL | 443 |
| CVE-2022-26134 | Confluence OGNL Injection | CRITICAL | 8090 |
| CVE-2022-22965 | Spring4Shell | CRITICAL | 80, 8080 |
| CVE-2021-41773 | Apache 2.4.49 Path Traversal | CRITICAL | 80 |
| CVE-2017-5638 | Apache Struts Multipart RCE | CRITICAL | 80, 8080 |
| CVE-2019-11510 | Pulse Secure VPN File Read | CRITICAL | 443 |
| CVE-2020-5902 | F5 BIG-IP TMUI RCE | CRITICAL | 443 |
| CVE-2020-1472 | ZeroLogon | CRITICAL | 135, 445 |
| CVE-2021-34527 | PrintNightmare | CRITICAL | 445 |
| CVE-2018-13379 | Fortinet FortiOS LFI | CRITICAL | 443 |
| CVE-2023-20198 | Cisco IOS XE Web UI LPE | CRITICAL | 80, 443 |
| CVE-2023-46747 | F5 BIG-IP Auth Bypass | CRITICAL | 443 |
| CVE-2023-35078 | Ivanti EPMM Auth Bypass | CRITICAL | 443 |
| CVE-2021-3156 | Baron Samedit (sudo) | HIGH | 22 |
| CVE-2016-6662 | MySQL RCE via config | CRITICAL | 3306 |
| CVE-2022-0778 | OpenSSL Infinite Loop DoS | HIGH | 443 |
| CVE-2015-3306 | ProFTPD mod_copy RCE | CRITICAL | 21 |
| CVE-2019-19781 | Citrix ADC Path Traversal | CRITICAL | 443 |
| CVE-2023-44487 | HTTP/2 Rapid Reset DDoS | HIGH | 443, 80 |
| CVE-2018-11776 | Apache Struts Namespace RCE | HIGH | 80, 8080 |
| CVE-2012-1823 | PHP-CGI Argument Injection | HIGH | 80 |
| CVE-2022-30190 | Follina (MSDT) | HIGH | 80, 445 |
| CVE-2023-23397 | Outlook NTLM Theft | CRITICAL | 445, 25 |
| CVE-2009-3960 | Redis No-Auth Exposed | CRITICAL | 6379 |
| CVE-2017-8291 | Ghostscript Sandbox Escape | HIGH | 80 |
| CVE-2015-1635 | IIS HTTP.sys RCE (MS15-034) | CRITICAL | 80 |
| CVE-2022-47966 | ManageEngine SAML RCE | CRITICAL | 443, 8443 |
| CVE-2021-20016 | SonicWall SSL-VPN SQLi | CRITICAL | 443 |
| CVE-2023-4911 | Looney Tunables (glibc) | HIGH | 22 |
| CVE-2022-21907 | IIS HTTP Trailer RCE | CRITICAL | 80, 443 |
| CVE-2021-26084 | Confluence Widget OGNL | CRITICAL | 8090 |
| MISCONFIG-001 | MongoDB No-Auth | CRITICAL | 27017 |
| MISCONFIG-002 | Elasticsearch No-Auth | HIGH | 9200 |
| MISCONFIG-003 | VNC No-Auth | CRITICAL | 5900 |
| MISCONFIG-004 | Telnet Cleartext | HIGH | 23 |
| MISCONFIG-005 | Anonymous FTP | MEDIUM | 21 |
| MISCONFIG-006 | SNMP Default Community | MEDIUM | 161/udp |
// my_patterns.json
{
"metadata": { "schema_version": "1.0.0" },
"cve_patterns": [
{
"cve_id": "CVE-2024-XXXXX",
"name": "My Custom Finding",
"description": "...",
"affected_ports": [8888],
"affected_protocols": ["tcp"],
"service_keywords": ["custom-app"],
"banner_patterns": ["vulnerable-version"],
"version_min": "1.0.0",
"version_max": "1.4.9",
"cvss_score": 8.8,
"severity": "HIGH",
"exploitable_remotely": true,
"authentication_required": false,
"affected_systems": ["CustomApp <= 1.4.9"],
"remediation": "Upgrade to 1.5.0.",
"references": ["https://vendor.com/advisory"],
"tags": ["rce"]
}
]
}nvs -t 10.0.0.1 -p top-100 --cve-db my_patterns.jsonThe JSON report structure:
{
"report_id": "550e8400-e29b-41d4-a716-446655440000",
"generated_at": "2024-11-01T14:30:00",
"scanner_version": "1.0.0",
"scan_metadata": {
"targets": ["192.168.1.0/24"],
"ports_scanned": [22, 80, 443, ...],
"port_count": 100,
"scan_duration_seconds": 42.5,
"options": {
"udp_enabled": false,
"nmap_used": true,
"banners_grabbed": true,
"cve_mapping": true,
"min_cvss": 0.0
}
},
"executive_summary": {
"total_hosts_scanned": 254,
"live_hosts": 12,
"total_open_ports": 48,
"total_findings": 7,
"unique_cves": 5,
"findings_by_severity": { "CRITICAL": 2, "HIGH": 3, "MEDIUM": 2, "LOW": 0, "INFO": 0 },
"hosts_with_critical": 2
},
"risk_score": {
"score": 72,
"max_score": 100,
"label": "HIGH",
"methodology": "CVSS-weighted sum, capped at 100"
},
"hosts": [
{
"ip": "192.168.1.10",
"hostname": "web01.local",
"os_guess": "Ubuntu 22.04",
"os_accuracy": "95%",
"rtt_ms": 1.2,
"open_ports": [
{
"port": 80,
"protocol": "tcp",
"state": "open",
"service": "http",
"product": "Apache",
"version": "2.4.49",
"version_string": "Apache/2.4.49 (Debian)",
"banner": "Server: Apache/2.4.49 (Debian)\r\n...",
"tls": false,
"rtt_ms": 0.5
}
],
"vulnerabilities": [
{
"cve_id": "CVE-2021-41773",
"name": "Apache 2.4.49 Path Traversal / RCE",
"severity": "CRITICAL",
"cvss_score": 9.8,
"description": "...",
"affected_port": 80,
"exploitable_remotely": true,
"remediation": "Upgrade Apache to >= 2.4.51 immediately.",
"references": ["https://nvd.nist.gov/..."]
}
]
}
],
"findings_index": [
{
"cve_id": "CVE-2021-41773",
"severity": "CRITICAL",
"cvss_score": 9.8,
"affected_hosts": [{ "ip": "192.168.1.10", "port": 80 }],
"remediation": "Upgrade Apache to >= 2.4.51."
}
],
"recommendations": [
{
"action": "Upgrade Apache to >= 2.4.51 immediately.",
"priority": "CRITICAL",
"max_cvss": 9.8,
"cves": ["CVE-2021-41773"]
}
]
}# Run all tests with verbose output
python -m pytest tests/ -v
# Run with coverage report
python -m pytest tests/ --cov=scanner --cov-report=term-missing
# Run a single test module
python -m pytest tests/test_cve_mapper.py -v
# Run a specific test class
python -m pytest tests/test_port_scanner.py::TestPortScanner -v
# Run a specific test method
python -m pytest tests/test_host_discovery.py::TestInetChecksum::test_known_value -vTest coverage targets:
| Module | Tests | Coverage Focus |
|---|---|---|
host_discovery.py |
test_host_discovery.py |
Checksum, packet, sweep, CIDR |
port_scanner.py |
test_port_scanner.py |
PortResult, TCP/UDP states, parsing |
service_detector.py |
test_port_scanner.py |
Per-protocol banner parsers |
cve_mapper.py |
test_cve_mapper.py |
DB loading, all match dimensions |
report_generator.py |
test_cve_mapper.py |
All report sections |
utils.py |
test_host_discovery.py |
CIDR expansion, port parsing |
- Add a
_parse_myservice(banner: str) -> Dictfunction toservice_detector.py. - Register it in
ServiceDetector._HANDLERS:
_HANDLERS = {
...
9999: _parse_myservice,
}Edit data/cve_patterns.json following the schema above, or supply a separate file with --cve-db.
Subclass or extend ReportGenerator.generate():
class MyReportGenerator(ReportGenerator):
def generate(self, hosts, scan_info=None):
report = super().generate(hosts, scan_info)
report["my_section"] = self._build_my_section(hosts)
return reportfrom scanner import HostDiscovery, PortScanner, ServiceDetector, CVEMapper, ReportGenerator
from scanner.utils import expand_cidr, parse_port_range
ips = expand_cidr("10.0.0.0/24")
ports = parse_port_range("22,80,443,8080")
live = HostDiscovery(max_workers=50).sweep(ips)
det = ServiceDetector()
hosts = []
for h in live:
scanner = PortScanner(timeout=1.0, max_workers=100)
prs = scanner.scan(h["ip"], ports)
h["open_ports"] = [
{**pr.to_dict(), **det.detect(pr.port, pr.banner, pr.tls)}
for pr in prs
]
hosts.append(h)
mapper = CVEMapper(min_cvss=7.0)
for h in hosts:
h["vulnerabilities"] = mapper.map_host(h)
report = ReportGenerator().generate(hosts)| Scenario | Recommended Settings |
|---|---|
| Fast /24 survey | --discovery-threads 100 --threads 200 --timeout 0.5 --no-banners |
| Thorough single-host | --threads 50 --timeout 2.0 -p 1-65535 |
| Stealth / IDS evasion | --threads 10 --timeout 3.0 |
| Large /16 network | --discovery-threads 200 --threads 50 --timeout 0.3 --no-banners -p top-100 |
Note: Aggressive thread counts and short timeouts increase the chance of missing hosts behind rate-limiting firewalls. Adjust based on network conditions and engagement scope.
| Limitation | Details |
|---|---|
| No SYN scan | NVS uses full TCP connect. SYN scanning is faster and stealthier but requires raw sockets. Use Nmap directly if SYN scanning is required. |
| UDP accuracy | Without root, closed UDP ports are indistinguishable from filtered. Many UDP ports will show as open|filtered. |
| CVE matching recall | Pattern matching is heuristic. Absence of a finding does NOT mean a host is safe. Always combine with manual verification. |
| False positives | Banner-based matching can fire on hosts that display the software name but have already been patched. Verify version independently. |
| No IPv6 support | Host discovery and port scanning target IPv4 only. |
| No rate limiting | NVS does not throttle outbound packets. On congested links, reduce thread counts and increase timeouts. |
| Windows support | Raw ICMP sockets require Administrator on Windows; the TCP-ping fallback works without elevation. |
- Fork the repository and create a feature branch:
git checkout -b feature/my-improvement - Write tests for any new functionality in
tests/. - Ensure
python -m pytest tests/ -vpasses with no failures. - Follow PEP 8 style; add docstrings to all public functions and classes.
- Update
data/cve_patterns.jsonwith sources and references for any new CVE entries. - Open a Pull Request with a clear description of the change and its security rationale.
Contributions to cve_patterns.json must include:
- A valid CVE ID (or
MISCONFIG-NNNfor misconfigurations without a CVE). - A verified
cvss_scorefrom nvd.nist.gov. - At least one
referencesURL pointing to an official advisory. - A concise
remediationaction.
MIT License
Copyright (c) 2024 NVS Project
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Built for the security community. Test responsibly.
{ "cve_id": "CVE-2017-0144", "name": "EternalBlue – SMBv1 RCE", "description": "Full technical description …", "affected_ports": [445, 139], // TCP/UDP port numbers "affected_protocols": ["tcp"], // "tcp" | "udp" "service_keywords": ["microsoft-ds"], // substrings of service name "banner_patterns": ["windows"], // substrings searched in banner "version_min": null, // inclusive lower bound (or null) "version_max": null, // inclusive upper bound (or null) "cvss_score": 9.3, "cvss_vector": "AV:N/AC:M/Au:N/C:C/I:C/A:C", "severity": "CRITICAL", "exploitable_remotely": true, "authentication_required": false, "affected_systems": ["Windows XP", "Windows 7"], "remediation": "Disable SMBv1; apply MS17-010.", "references": ["https://nvd.nist.gov/vuln/detail/CVE-2017-0144"], "tags": ["rce", "worm", "ms17-010"] }