T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/benchmark_multicloud_docs_api.py:107
- Finding
- Insufficient URL Validation Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/benchmark_multicloud_docs_api.py:107-109, 157-170, 288-297, 533-537` **Vulnerability Type**: Server-Side Request Forgery (SSRF) through improper hostname validation **Risk Level**: Medium ### Vulnerable Code ```python def domain_allowed(url: str, domains: tuple[str, ...]) -> bool: low = url.lower() return any(d in low for d in domains) ``` The weak validation is applied to links supplied through command-line arguments: ```python manual = getattr(args, f"{p.key}_links", "").strip() if manual: links = [x.strip() for x in manual.split(",") if x.strip()] links = [u for u in links if domain_allowed(u, p.domains)] source_tier = "L0" confidence = "high" if links else "low" notes = ["Using user-pinned official links."] ``` Accepted URLs are subsequently fetched: ```python def classify_links(links: list[str], max_fetch: int = 3) -> dict[str, bool]: chunks = ["\n".join(links).lower()] for url in links[:max_fetch]: try: html = fetch_text(url, timeout=12).lower() except Exception: continue ``` The request function uses `urllib.request.urlopen`, which follows redirects without validating each resulting destination: ```python def fetch_text(url: str, timeout: int = 20) -> str: req = urllib.request.Request( url, headers={ "User-Agent": "Mozilla/5.0 (Codex MultiCloud Benchmark)", "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8", }, ) with urllib.request.urlopen(req, timeout=timeout) as resp: return resp.read().decode("utf-8", errors="ignore") ``` ### Technical Analysis The `domain_allowed` function searches the complete URL for an approved domain substring. It does not parse the URL or verify that the approved domain is the actual destination hostname. Consequently, a URL may contain an approved domain in its user-information, path, query string, or an attacker-co ...[truncated 2968 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse every URL with `urllib.parse.urlsplit` and reject malformed URLs. 2. Permit only HTTPS unless HTTP is explicitly required and justified: ```python parsed = urllib.parse.urlsplit(url) if parsed.scheme != "https": return False ``` 3. Reject embedded credentials by requiring both `parsed.username` and `parsed.password` to be absent. 4. Compare the normalized hostname rather than searching the complete URL: ```python host = (parsed.hostname or "").rstrip(".").lower() def host_allowed(host: str, domains: tuple[str, ...]) -> bool: return any( host == domain or host.endswith("." + domain) for domain in domains ) ``` 5. Resolve the hostname and reject every loopback, private, link-local, multicast, unspecified, and reserved address using Python's `ipaddress` module. Validate all returned DNS addresses, not only the first one, to reduce DNS rebinding exposure. 6. Reject unexpected ports or maintain a narrow allowlist such as TCP 443. 7. Disable automatic redirects or install a redirect handler that validates the scheme, hostname, port, and resolved IP address for every redirect destination before following it. 8. Apply the same validation immediately before each network request rather than relying only on filtering performed earlier in the workflow. 9. Impose response-size limits and restrictive connection/read timeouts to reduce resource-exhaustion risk. 10. Where possible, route outbound traffic through an egress proxy that independently blocks loopback, private, link-local, metadata, and unauthorized external destinations. 11. Add regression tests for user-information confusion, malicious subdomains, domain names in paths and query strings, encoded hostnames, IPv4 and IPv6 literals, redirects to private addresses, alternate numeric IP representations, and DNS records resolving to restricted address ranges. ]]>
