T09 · Insecure Skill Coding Practices
- Location
seo_audit_skill.py:147- Finding
Unrestricted User-Controlled URL Fetching Enables Server-Side Request Forgery
- Content
View full analysis
dict: """Fetch URL and return {html, status_code, error}.""" try: import requests resp = requests.get(url, headers={ "User-Agent": "Mozilla/5.0 (compatible; OpenClawSEOBot/1.0)" }, timeout=15) return {"html": resp.text, "status_code": resp.status_code, "error": None} except Exception as e: return {"html": "", "status_code": 0, "error": str(e)} ``` The function is invoked with the user-controlled audit and competitor URLs: ```python # Full audit print(f"Fetching {args.url}...") fetch = fetch_page(args.url) if fetch["error"]: print(f"ERROR: {fetch['error']}") sys.exit(1) if fetch["status_code"] != 200: print(f"WARNING: HTTP {fetch['status_code']} — partial audit") result = run_checks(fetch["html"], args.url, args.keyword, args.industry) if args.competitor: print(f"Fetching competitor {args.competitor}...") comp_fetch = fetch_page(args.competitor) if not comp_fetch["error"] and comp_fetch["status_code"] == 200: comp_result = run_checks(comp_fetch["html"], args.competitor, args.keyword, args.industry) ``` ### Technical Analysis Both `--url` and `--competitor` are passed directly to `requests.get()` without validation of: - URL scheme - Destination hostname - Resolved IP address - Destination port - Redirect destinations - Whether the destination belongs to a loopback, private, link-local, reserved, or cloud metadata network The `requests` library follows redirects by default. Consequently, even validating only the original hostname would not be sufficient: an attacker-controlled public endpoint could redirect the request to an internal destinatio ...[truncated 1792 chars]- Remediation
View remediation
