T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/read_douyin.py:32
- Finding
- Insufficient URL Validation Permits Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/read_douyin.py:32-49` - `scripts/capture_frames.py:42-63` **Vulnerability Type**: Server-Side Request Forgery (SSRF) through unvalidated user-supplied URLs **Risk Level**: Medium ### Vulnerable Code `scripts/read_douyin.py:32-49`: ```python def extract_video_id(text: str) -> str: """从分享文本中解析视频 ID。""" urls = re.findall(r"https?://[^\s\u201c\u201d]+", text) if not urls: raise ValueError("未在输入中找到链接") url = urls[0].rstrip('/') m = re.search(r'/video/(\d+)', url) if m: return m.group(1) m = re.search(r'(\d{15,})', url) if m: return m.group(1) # 短链:跟随 302 取真实地址 resp = requests.get(url, headers={"User-Agent": UA_MOBILE}, allow_redirects=True, timeout=20) m = re.search(r'/video/(\d+)', resp.url) if not m: m = re.search(r'(\d{15,})', resp.url) if not m: raise ValueError(f"无法从 {resp.url} 解析视频 ID") return m.group(1) ``` `scripts/capture_frames.py:42-63`: ```python def resolve_target(text: str) -> dict: """把用户输入解析成 (要打开的页面地址, 平台, 视频 ID)。""" url = first_url(text) if "douyin.com" in url: m = re.search(r"/video/(\d+)", url) or re.search(r"(\d{15,})", url) if m: vid = m.group(1) else: # 短链,跟随 302 try: resp = requests.get(url, headers={"User-Agent": UA_MOBILE}, allow_redirects=True, timeout=20) m = re.search(r"/video/(\d+)", resp.url) or re.search(r"(\d{15,})", resp.url) vid = m.group(1) if m else None except Exception: vid = None if not vid: raise ValueError(f"无法从 {url} 解析抖音视频 ID") return {"page_url": f"https://www.douyin.com/video/{vid}", "platform": "douyin", "video_id": vid, "warmup": "https://www.douyin.com/"} return {"page_url": url, "platform": "generic", "video_id": None, "warmup": None} `` ...[truncated 2841 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse every URL with `urllib.parse.urlsplit` rather than using regular expressions or substring checks. 2. Require HTTPS for Douyin short-link resolution. 3. Enforce an exact, case-normalized hostname allowlist, for example: - `douyin.com` - `www.douyin.com` - `v.douyin.com` 4. Reject userinfo, malformed ports, IP-literal hosts, and hostnames with unexpected suffixes. 5. Resolve the hostname before connecting and reject every address that is loopback, private, link-local, multicast, reserved, or unspecified using Python's `ipaddress` module. 6. Disable automatic redirects and validate each `Location` destination before following it. Apply the same scheme, hostname, and resolved-address validation at every redirect hop. 7. If generic URL capture must remain available, place it behind an explicit opt-in flag and clearly warn that it allows outbound navigation. 8. Run browser and HTTP operations in a network-restricted sandbox that cannot reach cloud metadata services or internal administrative networks. 9. Add regression tests covering deceptive hostnames, embedded credentials, alternate IP representations, IPv6 addresses, DNS rebinding, and external-to-private redirects. ]]>
