T09 · Insecure Skill Coding Practices
- Location
skill.py:8- Finding
Unrestricted URL Fetching Enables SSRF and Local File Disclosure
- Content
View full analysis
str: req = urllib.request.Request( url, headers={ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" }, ) with urllib.request.urlopen(req, timeout=timeout) as resp: return resp.read().decode("utf-8", "ignore") ``` ```python # Accept common Zight share URL formats, including: # - https://a.cl.ly/XXXXX # - https://share.zight.com/XXXXX if not re.match(r"^https?://", zight_url, re.I): zight_url = "https://" + zight_url try: html = _fetch_text(zight_url) except (HTTPError, URLError, TimeoutError, ValueError) as e: print(json.dumps({"error": f"Failed to fetch Zight URL: {e}"}, indent=2)) return ``` ```python transcript = "" captions_url = output.get("captions_url") if captions_url: try: vtt = _fetch_text(captions_url) transcript = _vtt_to_text(vtt) except Exception as e: transcript = f"Failed to fetch/parse captions: {e}" ``` ### Technical Analysis The skill documentation identifies `a.cl.ly` and `share.zight.com` as its supported hosts, but the implementation does not enforce either hostname. The primary `zight_url` is accepted from the caller and passed to `urllib.request.urlopen` as long as it starts with HTTP or HTTPS. This permits requests to arbitrary Internet hosts, loopback services, private-network addresses, link-local addresses, and cloud metadata endpoints. The HTML returned by the first request is untrusted. The skill extracts `captions_url` from the page's embedded `store` JSON and passes that value directly to the same generic fetch function. No scheme, hostname, port ...[truncated 2702 chars]- Remediation
View remediation
