T09 · Insecure Skill Coding Practices
- Location
scripts/fetch_article.py:22- Finding
Server-Side Request Forgery Through Unvalidated User-Supplied URLs
- Content
View full analysis
Vulnerability Details
File Location:
scripts/fetch_article.py:22-44, 180-185; related workflow guidance inSKILL.md:21-25, 92-93
Vulnerability Type: Server-Side Request Forgery (SSRF) caused by insufficient URL validation
Risk Level: HighVulnerable Code
python def fetch_wechat_article(url: str) -> dict: """ Fetch a WeChat public-account article. Args: url: Public-account article URL Returns: dict: Dictionary containing title, author, and content """ headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'Cache-Control': 'max-age=0', } try: response = requests.get(url, headers=headers, timeout=30)python def main(): if len(sys.argv) < 2: print("Usage: python fetch_article.py <article URL> [--json]") sys.exit(1) url = sys.argv[1] output_json = '--json' in sys.argv result = fetch_wechat_article(url)The related Skill workflow explicitly allows attempts to retrieve non-WeChat URLs:
markdown - If it is not a WeChat public-account link, still attempt to summarize it, but remind the user that this Skill is primarily optimized for public-account articles.Technical Analysis
The script passes a user-controlled command-line URL directly to
requests.get()without validating:- The URL scheme
- The destination hostname
- The resolved IP address
- The destination port
- Redirect destinations
- Whether the destination belongs to an approved WeChat domain
...[truncated 2251 chars]
- Remediation
View remediation
Remediation Suggestions
- Restrict retrieval to HTTPS URLs whose normalized hostname exactly equals
mp.weixin.qq.comor belongs to a narrowly defined, reviewed allowlist. - Reject URLs containing embedded credentials, fragments where unnecessary, malformed hostnames, nonstandard ports, or unsupported schemes.
- Resolve the hostname before connecting and reject every address in loopback, private, link-local, multicast, reserved, unspecified, and other non-public ranges for both IPv4 and IPv6.
- Disable redirects with
allow_redirects=False, or manually process redirects and repeat the full scheme, hostname, port, DNS, and IP validation for every redirect target. - Protect against DNS rebinding by ensuring that the validated address is the address used for the connection and by validating every newly resolved redirect host.
- Explicitly block cloud metadata destinations, including link-local metadata addresses, as defense in depth.
- Remove the instruction to fetch arbitrary non-WeChat URLs. Reject unsupported domains rather than attempting retrieval.
- Apply strict response-size and content-type limits to reduce resource-exhaustion risk.
- Enforce outbound firewall or proxy rules so the Skill runtime cannot reach internal networks or metadata services.
- Add automated tests covering direct private addresses, IPv6 loopback, alternative IP representations, embedded credentials, unusual ports, DNS rebinding scenarios, and public-to-private redirects.
- Restrict retrieval to HTTPS URLs whose normalized hostname exactly equals
