T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/seo_audit.py:97
- Finding
- Unrestricted URL Fetching Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/seo_audit.py:97-103`, `scripts/seo_audit.py:190-195`, and `scripts/seo_audit.py:271-282` **Vulnerability Type**: Server-Side Request Forgery (SSRF) **Risk Level**: High ### Vulnerable Code ```python def fetch_page(url): req = urllib.request.Request(url, headers={ "User-Agent": "Mozilla/5.0 (compatible; SEOAuditBot/1.0)" }) with urllib.request.urlopen(req, timeout=15) as response: return response.read().decode("utf-8", errors="replace"), response.geturl() ``` ```python def audit_url(url, keywords=None, competitors=None): if not url.startswith("http"): url = "https://" + url html, final_url = fetch_page(url) ``` ```python keywords = [k.strip() for k in args.keywords.split(",") if k.strip()] competitor_urls = [c.strip() for c in args.competitors.split(",") if c.strip()] print(f"Auditing: {args.url}", file=sys.stderr) result = audit_url(args.url, keywords=keywords) competitor_results = [] for comp_url in competitor_urls: print(f"Auditing competitor: {comp_url}", file=sys.stderr) try: comp_result = audit_url(comp_url, keywords=keywords) ``` ### Technical Analysis The primary URL and every competitor URL are attacker-controlled command-line inputs. They flow into `audit_url()` and then into `urllib.request.urlopen()` without validation of the destination host or resolved IP address. The `startswith("http")` condition is not a security control. It neither restricts input to well-formed public HTTP or HTTPS URLs nor rejects loopback, private-network, link-local, reserved, or cloud metadata addresses. `urllib.request.urlopen()` also follows HTTP redirects by default, while the code does not validate redirect destinations. Consequently, the process can be used as a network proxy to issue requests to resources that are reachable from the host running the audit but are not directly reachable by the user. ### Attack Path 1. An attacker sup ...[truncated 1645 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse input with `urllib.parse.urlsplit()` and allow only the exact `http` and `https` schemes. 2. Reject URLs containing credentials, malformed hostnames, ambiguous numeric IP representations, or unsupported ports. 3. Resolve the hostname before connecting and reject every address classified as loopback, private, link-local, multicast, reserved, or unspecified using Python's `ipaddress` module. 4. Protect against DNS rebinding by ensuring the validated address is the address used for the connection, or by applying equivalent outbound controls at the network layer. 5. Disable automatic redirects or implement a restricted redirect handler that repeats scheme, hostname, and resolved-address validation for every redirect target. 6. Consider an explicit allowlist of domains when the deployment has a known set of permitted audit targets. 7. Apply outbound firewall or proxy rules that prevent the process from reaching internal and metadata networks. 8. Return a clear validation error without including sensitive internal response data. A hardened implementation should validate both the original destination and every redirect immediately before the corresponding connection. ]]>
