- Location
- scripts/fetch_content.py:70
- Finding
- Unrestricted Content Fetching Enables SSRF and Uses a Shared Predictable Temporary File<![CDATA[
## Vulnerability Details
**File Location**: `scripts/fetch_content.py:70-126, 186-219`; invoked by `scripts/api_server.py:376-387`
**Vulnerability Type**: Server-side request forgery and unsafe temporary-file handling
**Risk Level**: High
### Vulnerable Code
```python
def fetch_url_curl(url: str, timeout: int = 5, ua: str = MOBILE_UA) -> Dict:
result = {
'url': url,
'success': False,
'title': '',
'content': '',
'source': 'curl',
'status': 0,
'error': ''
}
try:
r = subprocess.run(
[
'curl', '-s', '-L', '-A', ua,
'--max-time', str(timeout),
'-H', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
'-H',
'Accept: text/html,application/xhtml+xml,'
'application/xml;q=0.9,*/*;q=0.8',
'-o', '/tmp/_fetch_tmp.html',
'-w', '%{http_code}|%{size_download}|%{url_effective}',
url
],
capture_output=True,
text=True,
timeout=timeout + 3
)
if r.returncode != 0:
result['error'] = f'curl failed: {r.returncode}'
return result
parts = r.stdout.split('|')
status = int(parts[0]) if parts[0].isdigit() else 0
size = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 0
result['status'] = status
with open('/tmp/_fetch_tmp.html', 'rb') as f:
raw = f.read()
```
```python
def fetch_url(url: str, use_playwright: bool = True) -> Dict:
if not url or not url.startswith('http'):
return {
'url': url,
'success': False,
'error': 'invalid_url',
'title': '',
'content': '',
'source': '',
'status': 0
}
is_weixin = 'mp.weixin.qq.com' in url
is_sogou_link = 'sogou.com/link' in url or 'sogoucdn.com' in url
if i
...[truncated 2499 chars]
- Remediation
- <![CDATA[
## Remediation Suggestions
- Parse URLs with a standards-compliant URL parser and permit only `http` and `https`.
- Resolve the hostname and reject loopback, private, link-local, reserved, multicast, and unspecified addresses for both IPv4 and IPv6.
- Revalidate the destination after every redirect.
- Prevent DNS rebinding by connecting to the previously validated address while preserving the expected hostname for TLS.
- Apply an allowlist when the expected source domains are known.
- Disable automatic content fetching for unauthenticated requests or require explicit user opt-in.
- Run the fetcher in a sandbox with restricted network egress.
- Replace the fixed path with `tempfile.NamedTemporaryFile` or process content in memory.
- Ensure temporary files are private, unique, and deleted in a `finally` block.
]]>