T09 · Insecure Skill Coding Practices
Error
- Location
- tools/event_analyze.py:43
- Finding
- Unrestricted URL Fetching Enables SSRF and Local File Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `tools/event_analyze.py:43-57` and `tools/event_analyze.py:2169-2193` **Vulnerability Type**: Server-Side Request Forgery and local resource disclosure **Risk Level**: High ### Vulnerable Code ```python def extract_text_from_url(url, timeout=15): """Extract main text content from a URL.""" req = urllib.request.Request(url, headers={ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/131.0.0.0 Safari/537.36' }) try: with urllib.request.urlopen(req, timeout=timeout) as resp: html = resp.read().decode('utf-8', errors='replace') extractor = HTMLTextExtractor() extractor.feed(html) text = extractor.get_text() # Truncate to reasonable length return text[:3000] if len(text) > 3000 else text except Exception as e: return f"[无法提取URL内容: {e}]" ``` The URL is supplied directly through a command-line argument: ```python event_text = args.event or '' if args.url: print(f"📥 提取URL内容: {args.url}", file=sys.stderr) url_content = extract_text_from_url(args.url) if event_text: event_text = event_text + '\n\n原文摘要:\n' + url_content else: event_text = url_content if not event_text: print("ERROR: 请提供 --event 或 --url 参数", file=sys.stderr) sys.exit(1) ``` Retrieved content is also used to construct an external news query: ```python news_data = None if not args.skip_news: print("📰 获取事件相关新闻...", file=sys.stderr) search_terms = event_text[:50] news_data = run_tool('news_fetch.py', ['--query', search_terms, '--limit', '8']) ``` ### Technical Analysis The `--url` value is passed directly to `urllib.request.urlopen()` without validating its scheme, hostname, resolved IP address, port, or redirect destination. Consequently, the fetcher is not limited to public HTTPS news pages ...[truncated 2604 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Permit only explicitly approved schemes, preferably `https`. 2. Reject `file://`, `ftp://`, `data:`, and all other non-HTTPS schemes. 3. Parse URLs with `urllib.parse.urlsplit()` and reject: - Embedded credentials. - Empty or malformed hostnames. - Unexpected ports. - Hostnames such as `localhost`. 4. Resolve the hostname before connecting and reject every address in loopback, private, link-local, multicast, unspecified, and reserved ranges. 5. Explicitly block common cloud metadata destinations, including link-local metadata addresses. 6. Disable automatic redirects or validate the scheme, hostname, port, and resolved address again after every redirect. 7. Prevent DNS rebinding by connecting only to an address that was validated and ensuring the connection does not resolve the hostname independently to a different address. 8. Enforce a response byte limit while streaming instead of reading the complete response before truncation. 9. Restrict accepted content types to expected textual or HTML media types. 10. Do not automatically use fetched document content as an external search query. Construct queries from separately validated user-supplied keywords or require explicit consent. 11. Run network-fetching functionality in a sandbox with restricted filesystem access and an outbound network allowlist. 12. Return generic errors to callers and log detailed network errors only to a protected diagnostic channel. ]]>
