T09 · Insecure Skill Coding Practices
- Location
scripts/archive.py:53- Finding
Arbitrary Filesystem Write Through Path Traversal During Archive Extraction
- Content
View full analysis
Vulnerability Details
File Location:
scripts/archive.py, lines 53–67
Vulnerability Type: Unrestricted archive extraction path / arbitrary filesystem write
Risk Level: HighVulnerable Code
python def rebuild_directory(manifest, file_contents, base_dir="output"): for path, info in manifest["files"].items(): full_path = os.path.join(base_dir, path) os.makedirs(os.path.dirname(full_path), exist_ok=True) ftype = info.get("type", "text") if ftype == "dir": os.makedirs(full_path, exist_ok=True) elif ftype == "symlink": target = info.get("target") if os.path.lexists(full_path): os.remove(full_path) try: os.symlink(target, full_path) except OSError as e: print(f"[警告] 无法创建软链接 {path} -> {target}: {e}") open(full_path, 'w').write(f"# symbolic link: {target}\n") else: with open(full_path, "wb") as f: f.write(file_contents.get(path, b"")) print(f"目录结构已重建于: {os.path.abspath(base_dir)}")Technical Analysis
The archive manifest fully controls
path. The extraction routine passes this value directly toos.path.join(base_dir, path)without rejecting absolute paths, parent-directory components such as.., or platform-specific drive-qualified paths.A relative path such as
../../targetescapes the intended extraction directory after filesystem path resolution. An absolute archive path may causeos.path.jointo discardbase_direntirely. The subsequent directory creation and file-writing operations therefore act on attacker-selected filesystem locations.There is also a symlink-assisted variant. Because manifest entries are processed in their declared order, a crafted archive can first create a symbolic link inside the output tree that targets an external directory, then add a regular-file entry beneath that link. The later file operation follows the symbolic link and writes outside the ex ...[truncated 1839 chars]
- Remediation
View remediation
Remediation Suggestions
- Reject archive paths that are absolute, drive-qualified, empty, or contain
.or..path components. - Resolve the extraction root and every candidate destination to canonical absolute paths, then verify containment with
os.path.commonpathbefore any filesystem operation. - Account for platform-specific separators and Windows drive or UNC paths rather than validating only POSIX-style paths.
- Prevent writes through existing symbolic links. Validate every parent component or use descriptor-relative, no-follow filesystem operations where supported.
- Do not allow archive-created symlinks to influence later extraction. Extract regular files and directories into a controlled tree before handling symlinks, while still validating every destination.
- Validate symlink targets or represent links as inert placeholder files when archives are untrusted.
- Refuse duplicate and conflicting entries, such as a path declared as both a symlink and a parent directory.
- Perform all validation before creating any file or directory so that an invalid archive cannot leave a partially modified filesystem.
- Add regression tests covering
../traversal, absolute paths, Windows drive paths, UNC paths, nested traversal, pre-existing symlink parents, and archive-created symlink traversal.
A containment check should follow this general pattern:
python root = os.path.realpath(base_dir) candidate = os.path.realpath(os.path.join(root, archive_path)) if os.path.commonpath([root, candidate]) != root: raise ValueError(f"Archive path escapes output directory: {archive_path}")This check must be combined with explicit path-component validation and protections against symlink races; canonicalization alone is not sufficient in a concurrently modified directory.
- Reject archive paths that are absolute, drive-qualified, empty, or contain
