T09 · Insecure Skill Coding Practices
- Location
scripts/fetch.py:133- Finding
Server-Side Request Forgery Through Substring-Based URL Validation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/fetch.py:133-135andscripts/fetch.py:163-169wechat_article_fetcher.py:132-134andwechat_article_fetcher.py:170-175examples/fetch_wechat_article.py:134-166
Vulnerability Type: Server-Side Request Forgery caused by improper URL validation
Risk Level: HighVulnerable Code
python # Validate URL if not url or "mp.weixin.qq.com" not in url: logger.error("Please provide a valid WeChat Official Account article URL") return NoneThe validated value is subsequently passed directly to Playwright:
python logger.info(f"Accessing: {url}") try: await page.goto(url, wait_until="domcontentloaded") except Exception as e: logger.warning(f"domcontentloaded timed out, trying load: {e}") await page.goto(url, wait_until="load")The standalone example contains no equivalent hostname validation before calling
page.goto:python async def fetch_wechat_article(url: str = None): """Fetch a WeChat Official Account article.""" if url is None: url = CONFIG["url"] async with async_playwright() as p: browser = await p.chromium.launch( headless=CONFIG["headless"], args=["--no-sandbox", "--disable-dev-shm-usage"] ) page = await browser.new_page( user_agent=CONFIG["user_agent"], viewport={"width": 1920, "height": 1080} ) page.set_default_timeout(CONFIG["timeout"]) try: logger.info(f"Accessing WeChat article: {url}") try: await page.goto(url, wait_until="domcontentloaded") except Exception as e: logger.warning(f"domcontentloaded timed out, trying load: {e}") await page.goto(url, wait_until="load")Technical Analysis
The URL check tests whether the literal string
mp.weixin.qq.comoccurs anywhere in the supplied value. It does not parse the URL or verify its sche ...[truncated 2280 chars]- Remediation
View remediation
Remediation Suggestions
- Parse URLs using
urllib.parse.urlsplitand reject malformed values. - Require the HTTPS scheme.
- Compare the normalized hostname exactly:
python from urllib.parse import urlsplit parsed = urlsplit(url) if parsed.scheme != "https" or parsed.hostname != "mp.weixin.qq.com": raise ValueError("Only HTTPS URLs on mp.weixin.qq.com are permitted")- Reject embedded credentials, unexpected ports, fragments, and malformed hostname encodings.
- Resolve the hostname and reject every loopback, private, link-local, multicast, reserved, or unspecified IP address using Python's
ipaddressmodule. - Revalidate every redirect destination. Prefer disabling automatic redirects and handling each redirect explicitly.
- Apply network-level egress controls that prevent the browser container from reaching internal networks and cloud metadata services.
- Use one shared URL-validation function across the module, CLI, batch tool, and example implementation.
- Add tests for deceptive hostnames, user-info syntax, alternate IP representations, IPv6, DNS rebinding, and redirects to private destinations.
- Parse URLs using
