T09 · Insecure Skill Coding Practices
- Location
scripts/benchmark_multicloud_docs_api.py:128- Finding
Official-Domain Allowlist Bypass Enables Server-Side Request Forgery
- Content
View full analysis
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: MediumComplete 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: continuepython 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.comThe accepted URLs are passed to
classify_links(), which callsfetch_text(). That function ultimately usesurllib.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
-
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
View remediation
Remediation Suggestions
-
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 ) -
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.
-
Restrict or reject nonstandard destination ports unless they are explicitly required.
-
Disable automatic redirects or validate the scheme, hostname, port, and resolved addresses of every redirect destination before following it.
-
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.
-
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.
-
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]
-
