T09 · Insecure Skill Coding Practices
- Location
scripts/unzip_all.py:35- Finding
ZIP Path Traversal Allows Arbitrary File Overwrite
- Content
View full analysis
Vulnerability Details
File Location:
scripts/unzip_all.py, lines 35–44
Vulnerability Type: Archive path traversal (Zip Slip)
Risk Level: HighVulnerable Code
python original = info.filename try: decoded = original.encode('cp437').decode('gbk') except: decoded = original target_path = os.path.join(extract_to, decoded) target_dir = os.path.dirname(target_path) if target_dir and not os.path.exists(target_dir): os.makedirs(target_dir, exist_ok=True) if not decoded.endswith('/'): with open(target_path, 'wb') as f: f.write(zf.read(info))Technical Analysis
The archive member name is joined directly to the extraction directory without validating the resulting canonical path. A malicious ZIP entry can contain parent-directory components such as
../, an absolute path, a Windows drive-qualified path, or equivalent separator combinations.os.path.join()does not guarantee that the resulting path remains underextract_to. The code subsequently creates parent directories and opens the resulting path usingwb, which truncates and overwrites an existing file.Filename recoding does not provide a security boundary. Validation must occur after decoding and path normalization. The code also does not check whether an existing destination component is a symbolic link or other filesystem redirection mechanism.
Attack Path
- An attacker creates a ZIP archive containing a member such as
../../target-file. - The attacker supplies the archive to a user or system that invokes this skill.
- The skill joins the malicious member name to the selected extraction directory.
- The normalized filesystem path escapes the intended extraction directory.
- The skill creates any required parent directories and opens the escaped path with write-and-truncate semantics.
- The attacker-controlled archive content overwrites the targeted file.
- If the targe ...[truncated 768 chars]
- An attacker creates a ZIP archive containing a member such as
- Remediation
View remediation
Remediation Suggestions
- Reject archive member names that are absolute, UNC-based, or Windows drive-qualified.
- Resolve the extraction root and every candidate destination to canonical absolute paths.
- Require every resolved candidate to remain strictly beneath the canonical extraction root, preferably using
os.path.commonpath()rather than string-prefix comparison. - Normalize both slash styles when archives may originate from different operating systems.
- Reject
..path components before writing. - Detect and reject symbolic links and other special archive entries.
- Prevent traversal through pre-existing symbolic-link components in the destination path.
- Consider creating files with exclusive, non-overwriting semantics unless overwriting is explicitly requested.
- Perform all safety checks after filename decoding and immediately before opening the destination.
- Apply equivalent containment protections to extraction performed through 7-Zip, using a staging directory and validating the resulting tree before moving files into the final destination.
A hardened implementation should follow this pattern:
python from pathlib import Path, PurePosixPath root = Path(extract_to).resolve() member = PurePosixPath(decoded.replace("\\", "/")) if member.is_absolute() or ".." in member.parts: raise ValueError("Unsafe archive member path") candidate = (root / Path(*member.parts)).resolve() if candidate == root or root not in candidate.parents: raise ValueError("Archive member escapes extraction directory")
