T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/wechat_publisher.py:174
- Finding
- Arbitrary Image URL Fetching Enables SSRF and Local File Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wechat_publisher.py`, lines 174-216 and 226-256 **Vulnerability Type**: Server-Side Request Forgery (SSRF) and unrestricted local resource access **Risk Level**: High ### Vulnerable Code ```python def upload_image_from_url(self, image_url: str) -> str: """从 URL 下载图片并上传到微信素材库,返回微信 CDN URL""" token = self.get_access_token() # 下载图片 print(f" ⬇️ 下载图片: {image_url[:60]}...") try: with urllib.request.urlopen(image_url, timeout=15) as resp: image_data = resp.read() content_type = resp.headers.get("Content-Type", "image/jpeg") except Exception as e: raise RuntimeError(f"图片下载失败: {e}") from e # 确定文件扩展名 ext = "jpg" if "png" in content_type: ext = "png" elif "gif" in content_type: ext = "gif" elif "webp" in content_type: ext = "webp" # 上传到微信(使用 uploadimg 接口,返回永久 URL) upload_url = f"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token={token}" boundary = "----FormBoundaryX7MA4YWxkTrZu0gW" filename = f"image.{ext}" body = ( f"--{boundary}\r\n" f'Content-Disposition: form-data; name="media"; filename="{filename}"\r\n' f"Content-Type: {content_type}\r\n\r\n" ).encode("utf-8") + image_data + f"\r\n--{boundary}--\r\n".encode("utf-8") req = urllib.request.Request( upload_url, data=body, headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}, method="POST", ) with urllib.request.urlopen(req, timeout=30) as resp: result = json.loads(resp.read().decode("utf-8")) ``` The same issue is present in the cover-image path: ```python def upload_cover_image(self, image_url: str) -> str: """上传封面图并返回 thumb_media_id(用于草稿接口)""" token = self.get_access_token() # 下载图片 with urllib.request.urlopen(image_url, timeout=15) as resp: image_data = resp.read() content_type ...[truncated 2540 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Permit only `https` URLs. 2. Restrict downloads to an explicit allowlist of expected image CDN domains. 3. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved addresses for both IPv4 and IPv6. 4. Repeat destination validation after every redirect, or disable automatic redirects. 5. Prevent DNS rebinding by connecting to a validated resolved address while preserving certificate and hostname verification. 6. Reject URLs containing embedded credentials. 7. Stream the response with a strict byte limit instead of calling unbounded `resp.read()`. 8. Verify both the declared MIME type and image file signature using a trusted image parser. 9. Decode and re-encode images before upload to eliminate polyglot or malformed content. 10. Apply connection, read, and total-operation timeouts. 11. If arbitrary external images are required, perform retrieval in a sandbox with no private-network access and minimal filesystem permissions. ]]>
