T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/scan_headers.py:201
- Finding
- Unrestricted URL Fetching Enables SSRF and Internal Network Reconnaissance## Vulnerability Details **File Location**: `scripts/scan_headers.py`, lines 201–211; user-controlled URL input is accepted at lines 581 and 592 **Vulnerability Type**: Server-Side Request Forgery (SSRF) through insufficient URL and destination validation **Risk Level**: High ### Vulnerable Code ```python def fetch_headers(url, timeout=10): """Fetch HTTP response headers from a URL.""" if not url.startswith(("http://", "https://")): url = "https://" + url ctx = ssl.create_default_context() req = Request(url, method="HEAD") req.add_header("User-Agent", "SecurityHeadersScanner/1.0") try: resp = urlopen(req, timeout=timeout, context=ctx) ``` The destination comes directly from a command-line argument: ```python parser.add_argument("urls", nargs="+", help="URL(s) to scan") ``` ```python for url in args.urls: results.append(scan_url(url)) ``` ### Technical Analysis Outbound HTTP access is necessary for the declared security-header scanning functionality. However, the implementation only tests whether the supplied string begins with `http://` or `https://`. It does not: - Parse and validate the destination hostname. - Reject loopback, private, link-local, unspecified, reserved, or multicast IP addresses. - Restrict destination ports. - Reject URLs containing embedded user credentials. - Resolve hostnames and validate every resulting address. - Revalidate destinations reached through HTTP redirects. - Protect against DNS rebinding or resolution changes between validation and connection. `urllib.request.urlopen` uses redirect handling by default. Consequently, an initially public URL may redirect the request to an internal or link-local destination without another security check. The request uses `HEAD`, which reduces response-body exposure but does not eliminate the vulnerability. Response status codes, headers, errors, and timing can reveal the ...[truncated 2041 chars]
- Remediation
- ## Remediation Suggestions 1. Parse each URL with `urllib.parse.urlsplit` and allow only explicitly supported `http` and `https` schemes. 2. Reject malformed URLs, embedded user information, missing hostnames, ambiguous numeric IP representations, and unsupported ports. 3. Resolve the hostname before connecting and inspect every returned address with Python's `ipaddress` module. 4. Reject loopback, private, link-local, unspecified, reserved, and multicast destinations for both IPv4 and IPv6. 5. Disable automatic redirects or implement a redirect handler that repeats full URL parsing, DNS resolution, address validation, and port validation for every redirect target. 6. Mitigate DNS rebinding by connecting to the validated resolved address while preserving the original hostname for TLS certificate verification and the HTTP `Host` header. 7. Restrict destination ports to an explicit allowlist, normally ports 80 and 443, unless broader access is required and authorized. 8. Prefer an explicit public-domain allowlist when the deployment context permits it. 9. Apply outbound firewall or proxy controls as defense in depth to deny access to internal, loopback, and link-local address ranges. 10. Document that URLs containing credentials, access tokens, session identifiers, or sensitive query parameters must not be scanned. 11. Add automated tests covering direct private IPs, IPv6 loopback, alternate IP encodings, DNS names resolving to private addresses, redirects to internal addresses, and DNS-rebinding scenarios.
