T09 · Insecure Skill Coding Practices
- Location
sub-skills/company-hk/scripts/handler.py:9- Finding
Redirect destinations bypass the outbound URL allowlist
- Content
View full analysis
Vulnerability Details
File Locations:
sub-skills/company-hk/scripts/handler.py:9-22, 39-42sub-skills/hk-candlesticks/scripts/handler.py:11-35, 124-127sub-skills/hk-valuatnanalyd/scripts/handler.py:9-22, 55-58sub-skills/hk-view/scripts/handler.py:9-22, 39-42
Vulnerability Type: Outbound request allowlist bypass through automatic redirects
Risk Level: MediumVulnerable Code
The following implementation is present in each handler:
python SAFE_URLOPENER = urllib.request.build_opener() BASE_URL = "https://market.ft.tech" def safe_urlopen(req_or_url): if isinstance(req_or_url, urllib.request.Request): url = req_or_url.full_url else: url = str(req_or_url) parsed = urllib.parse.urlparse(url) if parsed.scheme != "https" or parsed.netloc != "market.ft.tech": print(f"Invalid URL for safe_urlopen: {url}", file=sys.stderr) sys.exit(1) return SAFE_URLOPENER.open(req_or_url)The validated request is subsequently opened as follows:
python req = urllib.request.Request(url, method="GET") try: with safe_urlopen(req) as resp: data = json.loads(resp.read().decode()) print(json.dumps(data, ensure_ascii=False, indent=2))Technical Analysis
safe_urlopenverifies only the initial request URL. It requires the initial scheme to be HTTPS and the initial network location to equalmarket.ft.tech.However,
urllib.request.build_opener()includes Python's default redirect handling. If the approved endpoint returns an HTTP redirect, the opener can follow that redirect without invoking the validation logic again for the new destination. The redirect target may therefore use a different hostname or, depending on redirect handling, a less secure scheme.This creates a time-of-check versus time-of-use discrepancy: the URL that passes the allowlist check is not necessarily the final URL from ...[truncated 1612 chars]
- Remediation
View remediation
Remediation Suggestions
- Disable automatic redirects and reject redirect responses unless redirects are explicitly required.
- If redirects are required, implement a custom
HTTPRedirectHandlerthat validates every redirect target before following it. - Require the normalized destination scheme to be exactly
https. - Require the normalized hostname to be exactly
market.ft.tech. - Reject unexpected ports and user-information components.
- Resolve the hostname and block loopback, private, link-local, multicast, and reserved IP address ranges where appropriate.
- Apply the same validation after every redirect rather than only to the original request.
- Set a small maximum redirect count to prevent redirect loops.
Example hardening approach:
python class NoRedirectHandler(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): raise urllib.error.HTTPError( req.full_url, code, "Redirects are not permitted", headers, fp ) SAFE_URLOPENER = urllib.request.build_opener(NoRedirectHandler())If redirects must be supported, replace rejection with strict validation of
newurlbefore constructing the redirected request.
