Python zipfile 解压时如何防止路径穿越
接收用户上传的 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 解压看成“输入校验 + 目录边界 + 资源限制 + 逐个写入”四件事,才能避免只修复了一个路径样例,却把其他不可信输入留在流程里。
Go context.WithCancel 在长任务中如何按阶段提前释放
- 上一篇
- Go context.WithCancel 在长任务中如何按阶段提前释放
- 下一篇
- Go bufio.Scanner Split 自定义分词时为什么会漏掉最后一个 token
-
- 文章 · python教程 | 3小时前 | 正则表达式 · python · finditer · Python 正则表达式 re.finditer 重叠匹配
- Python re.finditer 处理重叠匹配时为什么会漏结果
- 193浏览 收藏
-
- 文章 · python教程 | 5小时前 | 默认值 · Python教程 · 数据类 · 对象初始化 · Python 可变默认值 default_factory dataclasses dataclasses.field
- Python dataclasses.field 默认工厂为什么不能直接写成列表
- 495浏览 收藏
-
- 文章 · python教程 | 5小时前 | 打包 · python · C扩展 · free-threading · wheel Python 3.15 abi3t cp315t
- Python 3.15 的 abi3t 与 cp315t wheel 如何选择
- 331浏览 收藏
-
- 文章 · python教程 | 11小时前 | python · typing · Annotated · 运行时反射 typing.Annotated Python类型注解
- Python typing.Annotated 的元数据怎么在运行时读取
- 347浏览 收藏
-
- 文章 · python教程 | 19小时前 | 并发 · 日志 · python · Python logging QueueHandler QueueListener
- Python logging QueueListener 停止时怎么保证剩余日志写完
- 496浏览 收藏
-
- 文章 · python教程 | 1天前 |
- Python asyncio.wait_for 超时后如何保留任务清理机会
- 469浏览 收藏
-
- 文章 · python教程 | 1天前 |
- Python asyncio.timeout 和 wait_for 的超时范围怎么选择
- 386浏览 收藏
-
- 文章 · python教程 | 1天前 | Windows · 跨平台 · Python教程 · 文件系统 · Python Python 3.15 os.path.isreserved Windows 保留路径 ntpath
- Python 3.15 os.path.isreserved 怎么判断 Windows 保留路径
- 343浏览 收藏
-
- 文章 · python教程 | 1天前 | Python教程 · pathlib · 文件系统 · 版本兼容 · Python 目录权限 Python 3.15 pathlib.Path.mkdir parent_mode
- Python pathlib.Path.mkdir 的 parent_mode 怎么影响中间目录
- 243浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 62次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 223次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 148次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 79次使用
-
- Generrated
- Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
- 58次使用
-
- Go语言文件锁操作
- 2023-01-07 225浏览
-
- Go语言文件的写入、追加、读取、复制操作
- 2022-12-30 389浏览
-
- Go语言从INI配置文件中读取需要的值
- 2022-12-23 250浏览
-
- Go语言并发目录遍历
- 2023-01-07 137浏览
-
- Go语言使用切片读写文件
- 2023-02-25 190浏览

