T09 · Insecure Skill Coding Practices
- Location
scripts/wechat_to_md.py:64- Finding
Unrestricted URL Fetching and Redirect Handling Permit Server-Side Request Forgery
- Content
View full analysis
str: raw = raw.strip() raw = html_unescape(raw) if raw.startswith("//"): raw = "https:" + raw if not raw.startswith("http"): raw = "https://" + raw return raw ``` The normalized URL is passed directly to Playwright: ```python page.goto(url, wait_until="domcontentloaded", timeout=timeout) ``` The fallback HTTP client follows redirects without validating their destinations: ```python resp = session.get(url, timeout=20, allow_redirects=True) resp.raise_for_status() resp.encoding = resp.apparent_encoding or "utf-8" return resp.text ``` Arbitrary HTTP image sources found in article content are accepted: ```python data_src = img.get("data-src", "") if data_src and ("mmbiz" in data_src or data_src.startswith("http")): img["src"] = data_src if data_src not in seen: seen.add(data_src) img_urls.append(data_src) src = img.get("src", "") if src and src not in seen and ("mmbiz" in src or src.startswith("http")): seen.add(src) img_urls.append(src) ``` Those image URLs are downloaded while redirects remain enabled: ```python resp = requests.get( url, timeout=timeout, headers=DEFAULT_HEADERS, stream=True, allow_redirects=True ) resp.raise_for_status() ``` ### Technical Analysis The documentation states that the Skill supports only `mp.weixin.qq.com` articles, but this restriction is not enforced in code. `normalize_url()` adds a scheme without validating: - The exact hostname - The URL scheme - The destination port - Embedded credentials - The resolved IPv4 or IPv6 address - Redirect destinations - Image CDN destinations Consequently, a caller can supply an arbitrary external, loopback, private-network, ...[truncated 2448 chars]- Remediation
View remediation
str: parsed = urlparse(url) if parsed.scheme != "https": raise ValueError("Only HTTPS URLs are allowed") if parsed.hostname != "mp.weixin.qq.com": raise ValueError("Only mp.weixin.qq.com is allowed") if parsed.username or parsed.password: raise ValueError("Embedded credentials are not allowed") if parsed.port not in (None, 443): raise ValueError("Nonstandard ports are not allowed") return url ``` 2. Resolve the hostname and reject every loopback, private, link-local, multicast, reserved, and unspecified IPv4 or IPv6 result. 3. Prevent DNS rebinding by validating the resolved address immediately before connection and ensuring the client connects to the validated destination. 4. Disable automatic redirects. If redirects are required, inspect and validate every redirect target before following it. 5. Apply a separate exact-host allowlist to images, limited to explicitly approved WeChat CDN domains. Do not use substring checks such as `"mmbiz" in URL`. 6. Validate image redirect destinations using the same hostname and IP-address controls. 7. Add outbound firewall or container-network rules that deny access to loopback, RFC1918 networks, link-local networks, cloud metadata addresses, and other sensitive ranges. 8. Add tests covering direct private addresses, IPv6 loopback, encoded hosts, user-information confusion, nonstandard ports, DNS rebinding, and external-to-internal redirects. ]]>
