直接调用 extractall() 会触发目录遍历,因其默认不校验路径,攻击者可通过含 ../ 的文件名(如 ../../etc/passwd)写入任意位置;Python 官方称其为「不安全的默认行为」。

为什么直接调用 extractall() 会触发目录遍历?
因为 zipfile.ZipFile.extractall() 默认不做路径校验,攻击者只要在 ZIP 文件中构造含 ../ 的文件名(比如 ../../etc/passwd),解压时就可能覆盖或写入任意位置。Python 官方文档也明确警告这是「不安全的默认行为」。
如何用 zipfile.Path + is_file() 做基础防护?
Python 3.12+ 引入了 zipfile.Path,它天然支持路径规范化和安全判断,但仅适用于只读场景——不能直接解压到磁盘。若你只需读取内容、验证结构,推荐这样写:
from zipfile import Path
<p>with zipfile.ZipFile("malicious.zip") as zf:
root = Path(zf)
for file in root.rglob("*"):
if file.is_file():</p><h1>检查路径是否在 ZIP 根下(自动处理 ../)</h1><pre class="brush:php;toolbar:false;"> if not str(file).startswith("safe_prefix/"):
raise ValueError(f"Unsafe path: {file}")
print("OK:", file)
注意:Path 不会帮你解压,它只是帮你「看清」ZIP 内部结构是否干净。
手动校验每个 ZipInfo.filename 是最通用方案
兼容 Python 3.6+,核心是:对每个 ZipInfo 实例,用 os.path.normpath() 归一化路径,再检查是否以目标解压目录为前缀。关键点:
-
ZipInfo.filename是原始字符串,可能含../、空字节、Windows 路径分隔符 - 必须用
os.path.sep而非硬写/,否则在 Windows 上会漏判..etchosts - 解压前需确保目标目录存在且不可写入父级(如
/tmp可被普通用户写,但/不行)
实操建议:
import os
import zipfile
<p>def safe_extract(zip_path, extract_to):
with zipfile.ZipFile(zip_path) as zf:
for member in zf.filelist:</p><h1>归一化路径并移除驱动器盘符(Windows)</h1><pre class="brush:php;toolbar:false;"> safe_name = os.path.normpath(member.filename).replace("\", "/")
# 检查是否逃逸出 extract_to 目录
if safe_name.startswith("../") or "/../" in safe_name or safe_name.startswith("/"):
raise ValueError(f"Path traversal attempt: {member.filename}")
# 构造绝对目标路径
target = os.path.join(extract_to, safe_name)
# 确保 target 仍在 extract_to 下(双重保险)
if os.path.commonpath([extract_to, os.path.abspath(target)]) != extract_to:
raise ValueError(f"Outside target dir: {target}")
zf.extract(member, extract_to)
别忽略 ZipInfo.is_dir() 和权限问题
目录遍历不是唯一风险:ZipInfo.external_attr 可能设置危险权限(如 Linux 下的 setuid 位),is_dir() 判断不准还会导致 OSError: [Errno 20] Not a directory。所以:
- 对每个
member,先调用member.is_dir(),再决定是os.makedirs()还是zf.extract() - 解压后显式重设文件权限(如
os.chmod(target, 0o644)),避免继承 ZIP 中的异常权限位 - 禁用
zipfile.ZipFile.open()直接读取非普通文件(如符号链接),除非你明确需要该行为
真实项目里,路径校验逻辑常被写成独立函数反复调用;一旦发现可疑路径,立刻中断整个解压流程——宁可失败,也不能留后门。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











