T09 · Insecure Skill Coding Practices
- Location
scripts/fetch_wechat_article.py:118- Finding
WeChat Session Cookie Can Be Sent to an Attacker-Controlled Host
- Content
View full analysis
tuple: import requests as req if not url.startswith("https://mp.weixin.qq.com"): url = "https://mp.weixin.qq.com/s/" + url.split("/s/")[-1] 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", "Cookie": cookie, "Referer": "https://mp.weixin.qq.com/", "Accept": "text/html,application/xhtml+xml,*/*", "Accept-Language": "zh-CN,zh;q=0.9", } r = req.get(url, headers=headers, allow_redirects=True, timeout=30) ``` ### Technical Analysis The code decides whether a URL is a trusted WeChat URL by performing a string-prefix comparison: ```python url.startswith("https://mp.weixin.qq.com") ``` A string prefix is not equivalent to hostname validation. URLs such as the following pass this check even though their effective destination is controlled by an attacker: ```text https://mp.weixin.qq.com.attacker.example/article https://mp.weixin.qq.com@attacker.example/article ``` The function then attaches the authenticated WeChat session cookie to the request. The use of `allow_redirects=True` additionally means redirect handling is not constrained to the trusted WeChat origin. This behavior exceeds the minimum privileges required to retrieve public article content. A privileged WeChat administrative session should not be attached to a destination unless its parsed hostname has been strictly validated. ### Attack Path 1. The victim configures authenticated WeChat Platform cookies as directed by the Skill. 2. An attacker supplies a URL beginning with the trusted string but resolving to an attacker-controlled hostname. 3. The unauthentic ...[truncated 861 chars]- Remediation
View remediation
str: parsed = urlsplit(url) if parsed.scheme != "https": raise ValueError("Only HTTPS URLs are allowed") if parsed.hostname != "mp.weixin.qq.com": raise ValueError("Untrusted hostname") if parsed.username or parsed.password: raise ValueError("Embedded credentials are prohibited") if parsed.port not in (None, 443): raise ValueError("Unexpected port") return url ``` ]]>
