T09 · Insecure Skill Coding Practices
Error
- Location
- src/daily_fetch.py:378
- Finding
- Server-Side Request Forgery Through Unvalidated RSS Article URLs<![CDATA[ ## Vulnerability Details **File Location**: `src/daily_fetch.py:282-306`, `src/daily_fetch.py:378-397` **Vulnerability Type**: Server-Side Request Forgery (SSRF) **Risk Level**: High ### Vulnerable Code ```python def fetch_article_content(url, source_name, config): """抓取文章正文 - 保存完整内容供AI生成摘要""" try: headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'} timeout = config['fetch']['request_timeout'] max_retries = config['fetch']['max_retries'] delay = config['fetch']['retry_delay'] # 使用 trafilatura 抓取 if USE_TRAFILATURA: try: downloaded = trafilatura.fetch_url(url) if downloaded: text = trafilatura.extract( downloaded, include_comments=False, include_tables=False, no_fallback=True, target_language='zh' ) if text and len(text) > 100: return text[:config['output']['raw_content_length']] except Exception as e: logging.warning(f"[trafilatura] 抓取失败 {source_name}: {e}") # 备用:BeautifulSoup resp = fetch_with_retry(url, headers, timeout, max_retries, delay) ``` The URL is obtained from an RSS entry and passed directly to the vulnerable fetch routine: ```python for entry in feed.entries[:30]: title = entry.get('title', '').strip() url = entry.get('link', '') if not title or not url: continue # 检查时间 published = entry.get('published_parsed') or entry.get('updated_parsed') if published: pub_date = datetime(*published[:6]) if pub_date < cutoff_date: continue rss_summary = re.sub(r'<[^>]+>', '', entry.get('summary', '')) is_dup, article = deduplicator.is_duplicate(url, title, rss_summary) if is_dup: if article: items.append(article) ...[truncated 2827 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Accept only `https` article URLs and reject URLs containing credentials or malformed hostnames. 2. Maintain an allowlist mapping each RSS source to its permitted article domains. 3. Resolve the hostname before connecting and reject every loopback, private, link-local, multicast, unspecified, and reserved IPv4 or IPv6 address. 4. Disable automatic redirects or validate the scheme, hostname, and resolved address of every redirect target. 5. Protect against DNS rebinding by connecting to the already validated address while preserving TLS hostname verification. 6. Apply the same validation to both `trafilatura.fetch_url()` and the fallback request path. Prefer one centrally controlled HTTP client so policies cannot be bypassed. 7. Block access to cloud metadata addresses and internal networks through host-level egress controls as defense in depth. 8. Impose response-size and content-type limits before parsing or storing a response. 9. Add tests covering direct private-IP URLs, IPv6 loopback, alternative IP representations, DNS rebinding, and public-to-private redirects. ]]>
