Python zipfile 解压时如何防止路径穿越
来源:17golang原创
时间:2026-09-10 14:52:12 449浏览 收藏
接收用户上传的 ZIP 时,风险不在“能不能解压”,而在成员名最终会落到哪个目录。稳妥做法是先读取 ZipInfo.filename,把它解析到固定目标目录下,确认规范化后的路径仍在边界内,再写文件;同时限制成员数量、后缀和解压后大小。不要把一个来自网络的归档直接交给自定义路径拼接逻辑。
官方文档:https://docs.python.org/3/library/zipfile.html
ZipFile.extract()和extractall()会尝试清理绝对路径与..组件,但不可信归档仍应先检查。- 自定义写入时,用
Path.resolve()配合os.path.commonpath()判断是否越出目标目录。 - 路径安全之外,还要限制成员数量、文件大小、压缩比和允许的文件类型。
先把成员名约束在目标目录内
常见错误是直接写出 target_dir / info.filename。成员名如果带有绝对路径或 ../,字符串拼接并不会自动表达“只能留在目标目录”。判断时要先把目标目录转成绝对路径,再解析成员路径,并比较两者的共同路径。

下面的函数把“越界”当成明确异常。它还跳过目录成员,避免把目录当普通文件写入:
from pathlib import Path
import os
import zipfile
def safe_member_path(root: Path, member_name: str) -> Path:
# 先固定根目录,再解析成员名,避免直接拼接字符串。
root = root.resolve()
candidate = (root / member_name).resolve()
try:
# commonpath 比字符串 startswith 更可靠,不会把 /tmp/app2 误判为 /tmp/app。
inside = os.path.commonpath((str(root), str(candidate))) == str(root)
except ValueError:
# Windows 下不同盘符等路径无法求共同路径,按不可信输入拒绝。
inside = False
if not inside:
raise ValueError(f"成员路径越出目标目录: {member_name!r}")
return candidate
def extract_checked(archive: Path, output: Path) -> list[Path]:
# 只允许解压到调用方指定的目录,并保留每个被拒绝成员的异常信息。
output.mkdir(parents=True, exist_ok=True)
written = []
with zipfile.ZipFile(archive) as zf:
for info in zf.infolist():
if info.is_dir():
continue
destination = safe_member_path(output, info.filename)
destination.parent.mkdir(parents=True, exist_ok=True)
# 使用二进制流复制,避免把压缩包内容当作文本解码。
with zf.open(info) as source, destination.open("wb") as target:
while chunk := source.read(1024 * 1024):
target.write(chunk)
written.append(destination)
return written
这里的 resolve() 会把 .. 组件折叠掉,commonpath() 再确认结果仍以根目录为共同路径。不要只用 str(candidate).startswith(str(root)),相邻目录名可能造成误判。
解压前还要限制类型和资源开销
路径没有越界,也不等于归档安全。一个 ZIP 可以包含大量成员、超大的解压后文件,或把不该落盘的类型放入上传流程。ZipInfo 提供了 filename、file_size 和 compress_size,可以在写入前做一轮策略判断。

| 检查项 | 建议动作 | 原因 |
|---|---|---|
| 成员数量 | 例如最多 1000 个 | 避免目录项耗尽 |
| 单文件与总大小 | 分别设置上限 | 控制磁盘占用 |
| 文件类型 | 使用后缀白名单 | 减少落盘风险 |
| 压缩大小 | 记录并限制极端压缩比 | 尽早发现异常归档 |
如果业务只接收 CSV,可把允许后缀固定为 {".csv"},并使用 Path(info.filename).suffix.lower() 判断。大小限制要在复制前检查 info.file_size,复制循环中再累计实际写入字节,防止元数据与实际输出不一致。
旧写法的边界:extractall 与 ZipFile.Path
extractall() 适合信任度较高、目标目录隔离清楚的简单场景。Python 文档说明它会尝试防止绝对路径和 .. 成员名造成越界,但同时警告不要在未检查不可信归档的情况下直接提取。若业务需要后缀、数量、大小或审计拒绝原因,就应像上面的例子一样逐个处理。
另一个容易混淆的 API 是 ZipFile.Path。文档明确指出它不会替调用方清理 ZIP 内的文件名;使用它遍历不可信归档时,仍要自行做路径校验。无论选哪种 API,目标目录最好是一个专用临时目录,完成检查和业务解析后再把允许的结果移动到正式位置。
用三组输入做回归检查
至少准备普通相对路径、带 ../ 的路径、绝对路径三组成员名。验证点不是“函数有没有抛异常”这么简单,还要确认拒绝项没有在目标目录之外产生文件,普通文件能保持内容,目录成员不会被当作空文件写入。生产中再补上重复文件名、大小超限和坏 ZIP 的测试。
# 这些断言只检查路径判断,不创建或覆盖真实系统目录。
assert safe_member_path(Path("/srv/upload"), "docs/readme.txt") == Path("/srv/upload/docs/readme.txt")
for bad in ("../outside.txt", "/tmp/outside.txt"):
try:
safe_member_path(Path("/srv/upload"), bad)
except ValueError:
pass # 越界成员必须被拒绝。
else:
raise AssertionError(f"未拒绝成员: {bad}")
常见问题
只检查成员名是否包含 .. 可以吗?
不够。绝对路径、Windows 盘符、不同平台分隔符和规范化后的相邻目录都需要纳入判断,最终应比较解析路径与目标目录的共同路径。
extractall() 已经清理路径,还需要逐个校验吗?
只要归档来自不可信来源,或业务有类型、大小、数量和审计要求,就应该先读取成员元数据,再决定是否写入。
校验路径后还要防 ZIP 炸弹吗?
要。路径校验只解决落盘位置问题,不能替代单文件大小、总大小、压缩比和处理超时等资源控制。
小结:把 ZIP 解压看成“输入校验 + 目录边界 + 资源限制 + 逐个写入”四件事,才能避免只修复了一个路径样例,却把其他不可信输入留在流程里。
-
225 收藏
-
389 收藏
-
250 收藏
-
137 收藏
-
190 收藏
-
文章 · python教程 | 1小时前 | python · glob · pathlib · 文件路径 · 递归搜索 · Python pathlib Path.rglob 隐藏目录 Path.glob 递归匹配207 收藏
-
193 收藏
-
文章 · python教程 | 5小时前 | 默认值 · Python教程 · 数据类 · 对象初始化 · Python 可变默认值 default_factory dataclasses dataclasses.field495 收藏
-
331 收藏
-
347 收藏
-
496 收藏
-
文章 · python教程 | 1天前 | 命令行工具 · 子命令 · Python教程 · argparse · Python argparse 子命令 argparse subparsers 公共参数427 收藏
-
163 收藏
-
469 收藏
-
386 收藏
-
文章 · python教程 | 1天前 | Windows · 跨平台 · Python教程 · 文件系统 · Python Python 3.15 os.path.isreserved Windows 保留路径 ntpath343 收藏
-
文章 · python教程 | 1天前 | Python教程 · pathlib · 文件系统 · 版本兼容 · Python 目录权限 Python 3.15 pathlib.Path.mkdir parent_mode243 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习