T09 · Insecure Skill Coding Practices
- Location
utils/url_validator.py:79- Finding
SSRF Protection Can Be Bypassed Through Redirects and DNS Rebinding
- Content
View full analysis
Vulnerability Details
File Location:
utils/url_validator.py:79-89; affected network sinks includefetchers/browser.py:71,fetchers/twitter.py:132,fetchers/rss.py:25, andfetchers/youtube.py:53,121
Vulnerability Type: Server-Side Request Forgery through time-of-check/time-of-use DNS resolution and unvalidated redirects
Risk Level: HighVulnerable Code
utils/url_validator.py:79-89validates DNS results only once and then returns the original URL:python try: resolved = socket.getaddrinfo(hostname, None, socket.AF_UNSPEC, socket.SOCK_STREAM) except socket.gaierror: raise ValueError(f"Blocked: cannot resolve hostname '{hostname}'") for family, _, _, _, sockaddr in resolved: ip = ipaddress.ip_address(sockaddr[0]) for network in _BLOCKED_NETWORKS: if ip in network: raise ValueError( f"Blocked: '{hostname}' resolves to private address {ip}" ) return urlfetchers/browser.py:71subsequently performs a separate browser navigation that may resolve the hostname again and follow redirects:python await page.goto(url, wait_until="domcontentloaded", timeout=TIMEOUT_MS)The Twitter-specific browser implementation has the same behavior at
fetchers/twitter.py:132:python await page.goto(url, wait_until="domcontentloaded", timeout=30_000)fetchers/rss.py:25delegates URL retrieval and redirect handling tofeedparser:python feed = feedparser.parse(url, resolve_relative_uris=False)The YouTube fetcher passes the validated URL to
yt-dlp, which performs its own subsequent resolution and network requests. The subtitle path atfetchers/youtube.py:40-54is:python cmd = [ "yt-dlp", "--write-auto-sub", "--write-sub", "--sub-lang", lang, "--sub-format", "srt", "--skip-download", "-o", output_path, url, ] try: subpr ...[truncated 3500 chars]- Remediation
View remediation
Remediation Suggestions
- Disable automatic redirects for HTTP clients. Process redirects manually and run the complete scheme, hostname, port, and resolved-address validation on every
Locationtarget. - Prevent DNS time-of-check/time-of-use differences by connecting to a validated, pinned IP address while preserving the intended TLS hostname and HTTP
Hostvalue. Alternatively, verify the actual connected peer address before accepting response data. - Reject all non-global address classes rather than relying on a partial network list. Cover private, loopback, link-local, multicast, unspecified, reserved, carrier-grade NAT, documentation, and IPv4-mapped IPv6 addresses.
- For Playwright, register request-routing handlers before navigation and abort top-level, redirect, iframe, script, image, media, WebSocket, and other subresource requests whose resolved destinations are not allowed.
- Replace direct
feedparser.parse(url)retrieval with a hardened HTTP client that implements the redirect and peer-IP policy. Parse the resulting response bytes locally. - Apply equivalent network restrictions to yt-dlp, preferably by isolating it in a sandbox or network namespace that cannot access loopback, link-local, metadata, or private networks.
- Revalidate the final browser URL after navigation, but do not rely on final-URL validation alone because the sensitive request may already have occurred.
- Add regression tests for public-to-private redirects, redirect chains, DNS rebinding, mixed IPv4/IPv6 results, decimal or encoded IP representations, and browser subresource requests.
- Keep the MCP SSE transport bound to localhost by default. If external exposure is required, add authentication, authorization, rate limiting, and network-level egress controls.
- Disable automatic redirects for HTTP clients. Process redirects manually and run the complete scheme, hostname, port, and resolved-address validation on every
