T09 · Insecure Skill Coding Practices
- Location
scripts/read_wechat.py:121- Finding
Insufficient URL Validation Enables Server-Side Request Forgery
- Content
View full analysis
Vulnerability Details
File Location:
scripts/read_wechat.py, lines 29–31 and 121–123
Vulnerability Type: Server-Side Request Forgery caused by inadequate URL validation
Risk Level: MediumVulnerable Code
python req = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(req, timeout=20) as resp: page = resp.read().decode("utf-8", errors="replace")python url = sys.argv[1] use_json = "--json" in sys.argv if "mp.weixin.qq.com" not in url: print("⚠️ 不是微信公众号文章链接,请提供 mp.weixin.qq.com/s/xxx 格式的链接") sys.exit(1)Technical Analysis
The script intends to accept only public WeChat article URLs, but it validates the user-controlled URL using a substring test:
python "mp.weixin.qq.com" in urlThis does not establish that the parsed destination hostname is exactly
mp.weixin.qq.com. An attacker can place the trusted string in a URL path, query, user-information component, or attacker-controlled hostname. For example, the following URL passes validation while targeting a local service:text http://127.0.0.1:8080/?source=mp.weixin.qq.comA hostname such as
mp.weixin.qq.com.attacker.examplealso passes the substring check. The code subsequently passes the complete untrusted value tourllib.request.urlopen, which performs the network request.Additionally,
urlopenfollows HTTP redirects by default. Therefore, validating only the initial string would remain insufficient: an initially acceptable endpoint could redirect the request to localhost, a private network, or another prohibited destination.Attack Path
- An attacker provides a crafted URL containing the text
mp.weixin.qq.comwhile identifying an attacker-selected destination. - The substring check at lines 121–123 accepts the URL.
- The URL reaches
fetch_article. urllib.request.urlopenconnects to the attacker-selected host.- The script reads the response and attempts to parse it as an article.
- Pars ...[truncated 1320 chars]
- An attacker provides a crafted URL containing the text
- Remediation
View remediation
Remediation Suggestions
- Parse the URL with
urllib.parse.urlsplitand reject malformed values. - Require the
httpsscheme. - Require the normalized hostname to be exactly
mp.weixin.qq.com; do not use substring or suffix checks. - Reject embedded credentials and non-default ports.
- Restrict accepted paths to the intended public article route, such as
/sor/s/..., if compatible with supported WeChat URLs. - Resolve the hostname and reject every address that is loopback, private, link-local, reserved, multicast, or unspecified for both IPv4 and IPv6.
- Disable automatic redirects or implement a redirect handler that validates every redirect destination using the same scheme, hostname, port, and IP-address rules.
- Apply response-size limits to reduce resource-exhaustion risk from untrusted HTTP responses.
- Return generic network errors rather than exposing detailed internal connection information.
A hostname validation baseline could use the following principle:
python from urllib.parse import urlsplit parsed = urlsplit(url) if ( parsed.scheme != "https" or parsed.hostname != "mp.weixin.qq.com" or parsed.port not in (None, 443) or parsed.username is not None or parsed.password is not None ): raise ValueError("Only HTTPS URLs hosted on mp.weixin.qq.com are allowed")This must be supplemented with redirect validation and destination IP checks to prevent redirect-based SSRF and DNS-based bypasses.
- Parse the URL with
