T09 · Insecure Skill Coding Practices
- Location
scripts/read_wechat_article.py:104- Finding
Arbitrary Outbound Requests Through Inadequate URL Validation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/read_wechat_article.py, lines 21–28 and 104–107
Vulnerability Type: Server-Side Request Forgery (SSRF)
Risk Level: MediumVulnerable Code
python def fetch(url: str, timeout: int = 20): req = Request(url, headers={ 'User-Agent': UA, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Cache-Control': 'no-cache', 'Pragma': 'no-cache', }) with urlopen(req, timeout=timeout) as resp: charset = resp.headers.get_content_charset() or 'utf-8' return { 'status_code': getattr(resp, 'status', None), 'final_url': resp.geturl(), 'text': resp.read().decode(charset, errors='replace') }python url = args.url.strip() if 'mp.weixin.qq.com' not in url: print('URL must point to mp.weixin.qq.com', file=sys.stderr) return 2Technical Analysis
The application intends to restrict network requests to WeChat articles hosted on
mp.weixin.qq.com. However, it only checks whether that string occurs anywhere in the complete URL. It does not use the importedurlparsefunction to verify the actual hostname or scheme.An attacker can therefore place
mp.weixin.qq.comin a path, query string, fragment, username, or attacker-controlled hostname while directing the request to another destination. Examples that pass the substring check include:text http://127.0.0.1:8080/private?mp.weixin.qq.com http://169.254.169.254/latest/meta-data/?mp.weixin.qq.com https://attacker.example/article/mp.weixin.qq.com https://mp.weixin.qq.com.attacker.example/urlopenalso follows HTTP redirects by default. Even if the initial URL were validated correctly, a permitted endpoint could redirect the request to localhost, a private network service, a link-local metadata endpoint, or another unauthorized host unless every redirect ...[truncated 1971 chars]- Remediation
View remediation
Remediation Suggestions
- Parse and validate the URL before constructing the request:
python from urllib.parse import urlparse def validate_wechat_url(url: str) -> str: parsed = urlparse(url) if parsed.scheme != 'https': raise ValueError('Only HTTPS URLs are allowed') if parsed.hostname != 'mp.weixin.qq.com': raise ValueError('URL hostname must be mp.weixin.qq.com') if parsed.username is not None or parsed.password is not None: raise ValueError('Embedded credentials are not allowed') if parsed.port not in (None, 443): raise ValueError('Nonstandard ports are not allowed') return url- Do not use suffix matching unless subdomains are explicitly required. If they are required, enforce a DNS-label boundary:
python host == 'mp.weixin.qq.com' or host.endswith('.mp.weixin.qq.com')Do not use:
python host.endswith('mp.weixin.qq.com')because it would accept hosts such as
evilmp.weixin.qq.com.-
Disable automatic redirects or implement a redirect handler that validates the scheme, hostname, port, and resolved IP address of every redirect target before following it.
-
Resolve the destination hostname and reject loopback, private, link-local, multicast, reserved, and unspecified addresses using Python's
ipaddressmodule. Repeat this validation for every redirect and connection to mitigate DNS rebinding. -
Apply response size limits before reading the full body to reduce denial-of-service exposure.
-
Stop the workflow when article retrieval has a
blocked,partial, or error status instead of unconditionally passing generated files to downstream processing. -
Add regression tests covering:
127.0.0.1andlocalhost.- RFC 1918 private addresses.
169.254.169.254.- Hostname suffix confusion.
- Embedded credentials.
- Nonstandard ports.
- Redirects to prohibited destinations.
- DNS names resolving to private or link-local addresses.
