- Location
- scripts/product-to-video.py:68
- Finding
- Unrestricted media URL fetching permits SSRF and external relaying of internal responses<![CDATA[
## Vulnerability Details
**File Locations**:
- `scripts/product-to-video.py:68-70`, `scripts/product-to-video.py:101-133`, `scripts/product-to-video.py:439-452`
- `scripts/image-to-ad-video.py:69-71`, `scripts/image-to-ad-video.py:105-152`, `scripts/image-to-ad-video.py:458-470`
- `scripts/replicate-video.py:78-80`, `scripts/replicate-video.py:110-152`, `scripts/replicate-video.py:415-445`
**Vulnerability Type**: Server-side request forgery and sensitive-content relay
**Risk Level**: High
### Vulnerable Code
The product-to-video script classifies any HTTP or HTTPS string as a downloadable media URL:
```python
def is_url(path: str) -> bool:
"""Check if path is a URL."""
return path and path.startswith(("http://", "https://"))
```
It then fetches that URL without validating the hostname, resolved address, scheme security, or redirects:
```python
def download_image_from_url(url: str, timeout: int = 30) -> dict[str, Any]:
"""Download image from URL to a temporary file."""
try:
print(f" Downloading from URL: {url}")
response = requests.get(url, timeout=timeout, stream=True)
response.raise_for_status()
# Check file size
content_length = response.headers.get("Content-Length")
if content_length and int(content_length) > MAX_FILE_SIZE:
return {"status": "error", "error": f"Image too large: {content_length} bytes (max: {MAX_FILE_SIZE})"}
# Get extension
ext = get_extension_from_url(url)
# Create temp file
temp_dir = tempfile.gettempdir()
filename = f"shortvideo_{uuid.uuid4().hex}{ext}"
temp_path = os.path.join(temp_dir, filename)
# Download to temp file
downloaded_size = 0
with open(temp_path, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
downloaded_size += len(chunk)
if downl
...[truncated 3786 chars]
- Remediation
- <![CDATA[
## Remediation Suggestions
1. Prefer accepting local files or previously issued ShortVideo asset identifiers instead of fetching arbitrary URLs.
2. If remote fetching is required, enforce a strict URL policy:
- Permit only HTTPS.
- Reject embedded credentials and nonstandard ports unless explicitly required.
- Allowlist trusted media hosts where feasible.
3. Resolve the hostname and reject all loopback, private, link-local, multicast, unspecified, reserved, and documentation ranges for both IPv4 and IPv6.
4. Explicitly block cloud metadata hosts and addresses, including link-local metadata services.
5. Disable automatic redirects with `allow_redirects=False`. If redirects are necessary, impose a small redirect limit and repeat complete scheme, hostname, port, DNS, and IP validation for every hop.
6. Protect against DNS rebinding by ensuring that the validated address is the address used for the connection, or place downloads behind a hardened egress proxy with network-level filtering.
7. Enforce expected MIME types and validate file signatures rather than trusting URL extensions. Abort on a non-image response for image parameters and on a non-video response for video parameters.
8. Retain streaming size checks, but also impose connection/read timeouts and a maximum decompressed response size.
9. Do not automatically upload fetched content until validation succeeds.
10. Add tests covering loopback, RFC1918 ranges, IPv6 local addresses, metadata endpoints, decimal/hexadecimal IP representations, DNS aliases, redirect chains, and DNS-rebinding scenarios.
]]>