T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/benchmark_multicloud_docs_api.py:128
- Finding
- Official-Domain Allowlist Bypass Enables Server-Side Request Forgery## Vulnerability Details **File Location**: `scripts/benchmark_multicloud_docs_api.py:128-130, 286-290, 552-555` **Vulnerability Type**: URL allowlist bypass leading to server-side request forgery (SSRF) **Risk Level**: Medium ### Complete Code Snippets ```python def domain_allowed(url: str, domains: tuple[str, ...]) -> bool: low = url.lower() return any(d in low for d in domains) ``` ```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 ``` ```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)] ``` ### Technical Analysis The application attempts to constrain manually supplied and discovered URLs to official provider domains. However, `domain_allowed()` performs a case-insensitive substring search across the entire URL rather than parsing the URL and validating its hostname. Consequently, untrusted URLs pass validation whenever trusted-domain text appears anywhere in the URL. Examples include: ```text http://127.0.0.1/?docs.aws.amazon.com https://docs.aws.amazon.com.attacker.example/ https://attacker.example/path/help.aliyun.com ``` The accepted URLs are passed to `classify_links()`, which calls `fetch_text()`. That function ultimately uses `urllib.request.urlopen()`, causing the host running the skill to issue the request. Redirect targets are not independently validated. Therefore, even a URL whose initial hostname is genuinely allowlisted could redirect the client to an internal or attacker-controlled destination. ### Attack Path 1. An attacker or untrusted user supplies a crafted URL through a manual argument such as: ```bash python scripts/benchmark_multicloud_docs_api ...[truncated 1694 chars]
- Remediation
- ## Remediation Suggestions 1. Parse each URL with `urllib.parse.urlsplit()` and validate the parsed hostname rather than searching the complete URL: ```python import urllib.parse def domain_allowed(url: str, domains: tuple[str, ...]) -> bool: try: parsed = urllib.parse.urlsplit(url) except ValueError: return False if parsed.scheme != "https": return False if parsed.username is not None or parsed.password is not None: return False host = (parsed.hostname or "").rstrip(".").lower() if not host: return False return any( host == domain.lower() or host.endswith("." + domain.lower()) for domain in domains ) ``` 2. Resolve the hostname and reject every address that is loopback, private, link-local, multicast, unspecified, or otherwise reserved. Validate all returned IPv4 and IPv6 addresses to prevent DNS rebinding through mixed address sets. 3. Restrict or reject nonstandard destination ports unless they are explicitly required. 4. Disable automatic redirects or validate the scheme, hostname, port, and resolved addresses of every redirect destination before following it. 5. Apply the same validation to manual links, preset seed links, search results, metadata links, and any other URL source immediately before each network request. Validation only at ingestion is insufficient. 6. Consider using an outbound proxy or network sandbox that denies localhost, private ranges, link-local ranges, and cloud metadata endpoints independently of application-level checks. 7. Add regression tests for malicious inputs, including: - Trusted text in a query string or path. - Trusted-domain prefixes on attacker-controlled hostnames. - User-information hostname confusion. - Encoded and mixed-case hostnames. - IPv4 and IPv6 loopback/private addresses. - Public URLs that redirect to internal addresses. - DNS responses co ...[truncated 41 chars]
