T09 · Insecure Skill Coding Practices
- Location
scripts/extract_wechat_article.py:44- Finding
Server-Side Request Forgery Through Weak Hostname Validation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/extract_wechat_article.py:44-45, 68
Vulnerability Type: Server-Side Request Forgery (SSRF)
Risk Level: HighVulnerable Code
python def normalize_url(url: str) -> str: parsed = urllib.parse.urlparse(url) if "mp.weixin.qq.com" not in parsed.netloc: raise ValueError("not a mp.weixin.qq.com url") query = urllib.parse.parse_qs(parsed.query, keep_blank_values=True) query.setdefault("scene", ["1"]) new_query = urllib.parse.urlencode(query, doseq=True) return urllib.parse.urlunparse( (parsed.scheme or "https", parsed.netloc, parsed.path, parsed.params, new_query, parsed.fragment) )The resulting URL is subsequently requested without destination revalidation:
python with urllib.request.urlopen(req, timeout=20) as resp: raw = resp.read() charset = resp.headers.get_content_charset() or "utf-8"Technical Analysis
The validation checks whether the raw network-location string contains
mp.weixin.qq.com. This substring test does not verify that the parsed destination hostname is exactly the approved Weixin host.For example, the following URL passes the check:
text http://mp.weixin.qq.com@127.0.0.1/In this URL,
mp.weixin.qq.comis user-information, while127.0.0.1is the actual destination host. An attacker-controlled hostname containing the trusted string can pass for the same reason.Additionally,
urllib.request.urlopenfollows HTTP redirects by default. Even if the initial hostname were validated correctly, the script does not validate redirect destinations, allowing a permitted endpoint to redirect the request to localhost, a private network, or another attacker-selected host.Attack Path
- An attacker supplies a URL whose
netloccontainsmp.weixin.qq.com, but whose actual hostname points to an unauthorized destination. normalize_url()accepts the URL because the substring is present.- The recons ...[truncated 1198 chars]
- An attacker supplies a URL whose
- Remediation
View remediation
Remediation Suggestions
- Validate the parsed hostname rather than searching the raw
netloc:
python parsed = urllib.parse.urlparse(url) if parsed.scheme != "https": raise ValueError("only HTTPS URLs are allowed") if parsed.hostname != "mp.weixin.qq.com": raise ValueError("host is not allowed")- Reject URL credentials and unexpected ports:
python if parsed.username is not None or parsed.password is not None: raise ValueError("URL credentials are not allowed") if parsed.port not in (None, 443): raise ValueError("non-default ports are not allowed")-
Resolve the hostname and reject loopback, private, link-local, reserved, multicast, and unspecified addresses. Perform this protection at connection time to reduce DNS rebinding and time-of-check/time-of-use risk.
-
Disable automatic redirects or implement a redirect handler that validates every redirect target using the same scheme, hostname, port, credential, and resolved-address restrictions.
-
Maintain an explicit allowlist of approved Weixin hostnames rather than accepting suffixes or substring matches.
-
Add regression tests covering user-information URLs, deceptive subdomains, attacker-controlled domains containing the trusted text, alternate ports, redirects to private addresses, IPv4 and IPv6 loopback targets, and DNS responses resolving to restricted address ranges.
- Validate the parsed hostname rather than searching the raw
