T09 · Insecure Skill Coding Practices
- Location
scripts/monitor.py:29- Finding
Unrestricted URL Fetching Enables SSRF and Local File Reads
- Content
View full analysis
Vulnerability Details
File Location:
scripts/monitor.py, lines 29-38
Vulnerability Type: Server-Side Request Forgery and unsafe URL scheme handling
Risk Level: HighVulnerable 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 NoneThe 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
--urlcommand-line ...[truncated 2258 chars]- Remediation
View remediation
Remediation Suggestions
- Parse every supplied URL with
urllib.parse.urlsplit()and allow only explicitly required schemes, preferablyhttps. - Reject URLs containing embedded credentials, malformed hosts, unsupported ports, or missing hostnames.
- Resolve the hostname before connecting and reject all loopback, private, link-local, multicast, reserved, unspecified, and metadata-service addresses using Python's
ipaddressmodule. - Protect against DNS rebinding by ensuring the address actually used for the connection remains an approved public address.
- Disable automatic redirects or validate the scheme, hostname, port, and resolved address of every redirect target before following it.
- Consider a domain allowlist when the monitored competitors are known in advance.
- Apply response-size and content-type limits to prevent memory or storage exhaustion.
- Do not store response content from rejected or partially validated destinations.
- 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.
- Parse every supplied URL with
