T09 · Insecure Skill Coding Practices
- Location
scripts/scan_headers.py:201- Finding
Unrestricted URL Fetching Enables SSRF and Internal Network Reconnaissance
- Content
View full analysis
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: HighVulnerable 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://orhttps://. 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.urlopenuses 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
View remediation
Remediation Suggestions
- Parse each URL with
urllib.parse.urlsplitand allow only explicitly supportedhttpandhttpsschemes. - Reject malformed URLs, embedded user information, missing hostnames, ambiguous numeric IP representations, and unsupported ports.
- Resolve the hostname before connecting and inspect every returned address with Python's
ipaddressmodule. - Reject loopback, private, link-local, unspecified, reserved, and multicast destinations for both IPv4 and IPv6.
- Disable automatic redirects or implement a redirect handler that repeats full URL parsing, DNS resolution, address validation, and port validation for every redirect target.
- Mitigate DNS rebinding by connecting to the validated resolved address while preserving the original hostname for TLS certificate verification and the HTTP
Hostheader. - Restrict destination ports to an explicit allowlist, normally ports 80 and 443, unless broader access is required and authorized.
- Prefer an explicit public-domain allowlist when the deployment context permits it.
- Apply outbound firewall or proxy controls as defense in depth to deny access to internal, loopback, and link-local address ranges.
- Document that URLs containing credentials, access tokens, session identifiers, or sensitive query parameters must not be scanned.
- 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.
- Parse each URL with
