T09 · Insecure Skill Coding Practices
Error
- Location
- mineru_api.py:220
- Finding
- Unvalidated ZIP Extraction Allows Path Traversal and Arbitrary File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `mineru_api.py`, lines 220–239 **Vulnerability Type**: Unsafe archive extraction **Risk Level**: High ### Vulnerable Code ```python def download(data: dict, out_dir=None) -> str: """Download results to the specified directory""" url = data.get("full_zip_url") if not url: return None # If no directory is specified, use the default result directory if out_dir is None: out_dir = "result" zip_path = os.path.join(out_dir, "result.zip") os.makedirs(out_dir, exist_ok=True) print(f"📥 Downloading...") r = requests.get(url, stream=True) with open(zip_path, "wb") as f: for c in r.iter_content(8192): f.write(c) with zipfile.ZipFile(zip_path, "r") as z: z.extractall(out_dir) os.remove(zip_path) return out_dir ``` ### Technical Analysis The `download()` function obtains an archive URL from the MinerU API response, downloads the content, and passes the resulting archive directly to `ZipFile.extractall()` without explicitly validating archive member paths. A malicious or compromised archive can contain absolute paths or traversal components such as `../../`. If these entries are not rejected by the deployed Python runtime or extraction environment, extraction can cause files to be written outside the intended `~/.openclaw/MinerU_Results/` directory. The download operation also lacks an HTTP timeout, response status validation, content-type validation, archive-size limit, and integrity verification. Consequently, an invalid or unbounded response could be stored and processed, increasing the potential for denial of service or processing of attacker-controlled content. ### Attack Path 1. An attacker compromises or impersonates the MinerU result service, its storage endpoint, or another component capable of influencing the `full_zip_url` response. 2. The attacker supplies a ZIP archive containing crafted entries such as ...[truncated 1410 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Validate every archive member before extraction: - Reject absolute paths. - Reject drive-qualified paths. - Reject `..` traversal components. - Resolve each destination path and verify that it remains under the canonical output directory. - Reject symbolic links and other special file types unless explicitly required. 2. Extract members individually only after validation rather than calling `extractall()` directly. 3. Harden the download operation: - Require an HTTPS URL from an explicitly approved host or trusted allowlist. - Add connect and read timeouts. - Call `raise_for_status()` before writing the response. - Enforce a maximum compressed download size. - Enforce limits on member count, individual extracted size, and total extracted size. - Reject unexpected content types where practical. 4. Validate archive integrity before extraction: - Use a trusted digest or signature supplied through an authenticated channel when available. - Verify that the downloaded file is a valid ZIP archive before processing it. 5. Use a private temporary directory and clean it up in a `finally` block. Example path-validation pattern: ```python def safe_extract(zip_file, destination): destination = Path(destination).resolve() for member in zip_file.infolist(): member_path = Path(member.filename) if member_path.is_absolute() or ".." in member_path.parts: raise ValueError(f"Unsafe archive member: {member.filename}") target = (destination / member_path).resolve() try: target.relative_to(destination) except ValueError: raise ValueError(f"Archive member escapes destination: {member.filename}") # Reject symbolic links and other special entries as appropriate. mode = member.external_attr >> 16 if stat.S_ISLNK(mode): raise ValueError(f"Symbolic links are not allowed: {member.filename}" ...[truncated 339 chars]
