T09 · Insecure Skill Coding Practices
- Location
scripts/build_digest.py:491- Finding
Feed-Controlled Server-Side Request Forgery in Automatic Article Retrieval
- Content
View full analysis
tuple[str, str, str]: request = urllib.request.Request( url, headers={ "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0 Safari/537.36" ) }, ) with urllib.request.urlopen(request, timeout=timeout) as response: content_type = response.headers.get("Content-Type", "") if "text/html" not in content_type and "application/xhtml+xml" not in content_type: raise ValueError(f"Unsupported content type: {content_type}") payload = response.read(1_500_000).decode("utf-8", errors="replace") return payload, response.geturl(), content_type ``` The untrusted URL is passed into this function without destination validation: ```python html_text, final_url, _ = fetch_url(candidate.raw_url, timeout=timeout) ``` Article retrieval is performed automatically during normal digest construction: ```python url_deduped, url_removed = dedupe_exact_urls(raw_items) fetch_bodies(url_deduped, timeout=args.timeout, concurrency=args.concurrency) ``` ### Technical Analysis The article URL originates from the FreshRSS item's `canonical` or `alternate` field. A subscribed feed, compromised feed publisher, or malicious FreshRSS entry can therefore influence the destination requested by the digest host. The implementation does not validate: - The URL scheme. - The destination hostname. - The resolved IP address. - The destination port. - Whether the address is loopback, private, link-local, reserved, or otherwise non-public. - Redirect destinations followed automatically by `urllib.request.urlopen`. Checking the response con ...[truncated 1548 chars]- Remediation
View remediation
