T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/monitor.py:29
- Finding
- Unrestricted URL Fetching Enables SSRF and Local File Reads## Vulnerability Details **File Location**: `scripts/monitor.py`, lines 29-38 **Vulnerability Type**: Server-Side Request Forgery and unsafe URL scheme handling **Risk Level**: High ### Vulnerable Code ```python def fetch_page(url, timeout=15): """Fetch a URL and return text content.""" try: headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" } req = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(req, timeout=timeout) as resp: return resp.read().decode("utf-8", errors="replace") except Exception as e: print(f"Error fetching {url}: {e}", file=sys.stderr) return None ``` The attacker-controlled URL reaches this function through the competitor configuration: ```python def check_competitor(competitor): """Run all checks for a single competitor.""" name = competitor["name"] changes = [] print(f"\nChecking: {name}") tracks = competitor.get("trackingTypes", ["pricing", "blog"]) if "pricing" in tracks and competitor.get("pricingUrl"): result = check_page_changes(name, competitor["pricingUrl"], "pricing") if result: changes.append(result) if "blog" in tracks: blog_url = competitor.get("blogUrl", competitor["url"] + "/blog") result = check_page_changes(name, blog_url, "blog") if result: changes.append(result) if "changelog" in tracks: changelog_url = competitor.get("changelogUrl", competitor["url"] + "/changelog") result = check_page_changes(name, changelog_url, "changelog") if result: changes.append(result) # Main page check result = check_page_changes(name, competitor["url"], "main") if result: changes.append(result) ``` ### Technical Analysis The `--url` command-line ...[truncated 2258 chars]
- Remediation
- ## Remediation Suggestions 1. Parse every supplied URL with `urllib.parse.urlsplit()` and allow only explicitly required schemes, preferably `https`. 2. Reject URLs containing embedded credentials, malformed hosts, unsupported ports, or missing hostnames. 3. Resolve the hostname before connecting and reject all loopback, private, link-local, multicast, reserved, unspecified, and metadata-service addresses using Python's `ipaddress` module. 4. Protect against DNS rebinding by ensuring the address actually used for the connection remains an approved public address. 5. Disable automatic redirects or validate the scheme, hostname, port, and resolved address of every redirect target before following it. 6. Consider a domain allowlist when the monitored competitors are known in advance. 7. Apply response-size and content-type limits to prevent memory or storage exhaustion. 8. Do not store response content from rejected or partially validated destinations. 9. Add tests for `file://`, localhost, IPv4 and IPv6 loopback, private subnets, link-local addresses, encoded IP representations, redirects to internal hosts, and DNS rebinding scenarios.
