T09 · Insecure Skill Coding Practices
Warning
- Location
- nmap_scanner.py:16
- Finding
- Unvalidated Scan Target Allows Nmap Option Injection## Vulnerability Details **File Location**: `nmap_scanner.py`, lines 16–27 **Vulnerability Type**: Nmap argument and option injection **Risk Level**: Medium ### Vulnerable Code ```python if scan_type == "vulnerability": cmd = ["nmap", "-sV", "--script", "vuln", target, "-oX", "-"] elif scan_type == "service": cmd = ["nmap", "-sV", target, "-oX", "-"] elif scan_type == "os": cmd = ["nmap", "-O", target, "-oX", "-"] else: cmd = ["nmap", target, "-oX", "-"] try: # Run the scan result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) ``` ### Technical Analysis The caller-controlled `target` value is inserted directly into the Nmap argument list without validation. Although `subprocess.run()` is used without `shell=True`, which prevents conventional shell metacharacter injection, Nmap still interprets arguments beginning with a hyphen as command-line options. Consequently, a value intended to represent an IP address, CIDR range, or hostname can instead alter Nmap's behavior. For example, an input using Nmap's inline input-list option, such as `-iL/path/to/targets`, may cause Nmap to read scan targets from a local file rather than treating the value as a target. Other supported inline Nmap options may modify script selection, data directories, exclusions, or scan configuration. Because the value occupies one process argument, embedded spaces do not create multiple shell arguments. Exploitation is nevertheless possible through Nmap options that support attached or equals-delimited values. ### Attack Path 1. An application exposes `run_nmap_scan()` to a caller and passes caller-controlled input as `target`. 2. The attacker supplies a value beginning with `-`, such as an attached Nmap input-list option referencing a known local file. 3. The function adds the value to `cmd` without checking whether it is a valid IP address, network range, or hostname. 4. `subprocess.run() ...[truncated 1135 chars]
- Remediation
- ## Remediation Suggestions 1. Validate `target` before constructing the command: - Parse IP addresses and CIDR ranges with Python's `ipaddress` module. - If hostnames are supported, enforce a conservative hostname syntax and length limit. - Reject empty values, control characters, whitespace-separated target expressions, and all values beginning with `-`. - Explicitly decide whether multiple targets, wildcard expressions, or Nmap-specific target syntax are allowed. 2. Separate validation by supported target type rather than relying only on a blacklist. For example: ```python import ipaddress import re HOSTNAME_RE = re.compile( r"(?=.{1,253}\Z)" r"(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)*" r"[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\Z" ) def validate_target(target: str) -> str: if not isinstance(target, str) or not target or target.startswith("-"): raise ValueError("Invalid scan target") try: ipaddress.ip_network(target, strict=False) return target except ValueError: pass if HOSTNAME_RE.fullmatch(target): return target raise ValueError("Target must be an IP address, CIDR range, or hostname") ``` 3. Apply network authorization controls in addition to syntax validation. If the Skill is intended only for local networks, resolve hostnames and verify that every resulting address belongs to an explicitly approved network range. 4. Run Nmap with the minimum required operating-system privileges. Do not run the containing service as root unless a specific scan mode requires it. 5. Add regression tests covering: - Valid IPv4 and IPv6 addresses. - Valid CIDR ranges and hostnames. - Leading-hyphen inputs. - Attached options such as `-iL...`. - Equals-delimited long options. - Control characters, malformed CIDRs, and whitespac ...[truncated 85 chars]
