T09 · Insecure Skill Coding Practices
- Location
scripts/image_downloader.py:23- Finding
Unrestricted Remote Image Download Enables SSRF and Resource Exhaustion
- Content
View full analysis
bool: """Determine whether the string is an image/media URL.""" return path.lower().startswith(("http://", "https://")) ``` ```python # Download parsed = urlparse(image_url) headers = { "User-Agent": _USER_AGENT, "Referer": f"{parsed.scheme}://{parsed.hostname}/", } resp = self._session.get(image_url, headers=headers) if resp.status_code != 200: raise RuntimeError(f"Download failed (status={resp.status_code}): {image_url}") # Save with open(filepath, "wb") as f: f.write(resp.content) ``` ### Technical Analysis The downloader considers every HTTP or HTTPS address a valid image URL. It performs no validation of the destination hostname or resolved IP address and does not block loopback, private, link-local, reserved, or cloud metadata addresses. The request also follows redirects by default. Therefore, validating only the original URL would remain insufficient because a public endpoint could redirect the request to an internal destination. No content-type validation, image decoding validation, streaming byte limit, or maximum response size is applied. `resp.content` loads the complete response into memory before writing it to disk. Although the constructor assigns `self._session.timeout = 30`, `requests.Session` does not interpret that attribute as a default request timeout, and the individual request does not supply a `timeout` argument. ### Attack Path 1. An attacker or untrusted caller supplies a crafted value through the `--images` CLI option. 2. `process_images()` recognizes the value as remote input because it begins with `http://` or `https://`. 3. `download_image()` sends a request from the host running the Skill. 4. The destination ...[truncated 1177 chars]- Remediation
View remediation
