T09 · Insecure Skill Coding Practices
- Location
references/sucuang_api.md:125- Finding
Unvalidated Remote Media URL Enables Server-Side Request Forgery and Resource Exhaustion
- Content
View full analysis
Vulnerability Details
File Location:
references/sucuang_api.md, lines 125–141
Vulnerability Type: Unrestricted remote URL retrieval and unbounded archive processing
Risk Level: MediumVulnerable Code
python while True: result = requests.get(f"{BASE}/api/async/detail?id={task_id}", headers=HEADERS) data = result.json()["data"] if data["status"] == 2: # Complete audio_url = data["audio_url"] audio_resp = requests.get(audio_url) tar = tarfile.open(fileobj=io.BytesIO(audio_resp.content)) for member in tar.getmembers(): if member.name.endswith('.mp3'): f = tar.extractfile(member) with open(output_path, 'wb') as out: out.write(f.read()) return elif data["status"] == -1: # Failed raise Exception(f"TTS failed: {data}") time.sleep(3)Technical Analysis
The
audio_urlvalue is obtained from a remote API response and passed directly torequests.get()without validating its scheme, hostname, port, or resolved IP address. If the API service, API response, or task data is compromised, an attacker can supply a URL targeting an unintended internal or local service.The response is also accessed through
audio_resp.content, which loads the entire body into memory before parsing it as a tar archive. No HTTP timeout, status validation, content-length limit, archive-size limit, member-count limit, or decompressed-size limit is enforced. A malicious endpoint could therefore return an extremely large response or a highly compressed archive and exhaust process memory, CPU, or disk capacity.The implementation uses
extractfile()and writes the selected member to a fixedoutput_path, so the shown code does not directly perform path traversal through archive member names. The primary risks are unrestricted URL retrieval and resource exhaustion.Attack P
...[truncated 1430 chars]
- Remediation
View remediation
Remediation Suggestions
- Require
https://URLs and reject embedded credentials, fragments, unsupported ports, and malformed URLs. - Maintain an explicit allowlist of expected media-download hostnames.
- Resolve the destination before connecting and reject loopback, link-local, private, reserved, multicast, and cloud metadata address ranges. Repeat validation after redirects and DNS resolution changes.
- Disable redirects or validate every redirect destination.
- Set explicit connect and read timeouts:
python requests.get(audio_url, timeout=(5, 30), allow_redirects=False, stream=True) - Call
raise_for_status()before processing the body. - Stream the response while enforcing a strict maximum byte count instead of using
audio_resp.content. - Validate the response content type and archive signature.
- Limit the number of archive members, individual member size, total decompressed size, and compression ratio.
- Reject non-regular archive members and process only one expected MP3 member.
- Write to a temporary file with restrictive permissions and atomically replace the destination only after validation succeeds.
- Require
