Python zipfile ZipFile.extractall 如何检查危险路径
处理上传或下载来的 ZIP 文件时,不要把“extractall() 会尝试清理路径”当成完整安全方案。更稳妥的做法是先遍历 ZipInfo.filename,拒绝绝对路径、盘符路径和包含 .. 的成员,再把通过检查的成员交给解压逻辑;成员数量、展开后的总大小和磁盘空间还要单独限制。
路径校验的核心是证明每个目标路径都位于指定根目录内,而不是简单查找一个字符串。ZipFile.extractall() 只能作为最后的写入动作,不能替代对不可信归档的预检查。
先把成员名变成目录边界内的路径
ZIP 成员名通常使用斜杠,但攻击者可以混入反斜杠、绝对路径或父目录组件。下面的检查先统一分隔符,再用 PurePosixPath.parts 判断明显危险项,最后使用真实目标目录的绝对路径做边界比较。只保留检查结果为安全的 ZipInfo,不要重新拼接名字后绕过原始成员信息。

from pathlib import Path, PurePosixPath
import ntpath
import os
import zipfile
def safe_members(zf: zipfile.ZipFile, target: Path) -> list[zipfile.ZipInfo]:
# 目标目录先固定下来,后续所有成员都必须落在这个目录中。
root = target.resolve()
accepted = []
for info in zf.infolist():
# ZIP 名称统一为 POSIX 分隔符,同时检查 Windows 盘符写法。
name = info.filename.replace("\\", "/")
parts = PurePosixPath(name).parts
if PurePosixPath(name).is_absolute() or ntpath.splitdrive(name)[0]:
continue
if ".." in parts:
continue
candidate = (root / Path(*parts)).resolve()
# commonpath 相等或以 root 为前缀,才能证明没有越过解压根目录。
try:
inside = os.path.commonpath((str(root), str(candidate))) == str(root)
except ValueError:
# 不同盘符等无法比较的路径直接拒绝。
inside = False
if inside:
accepted.append(info)
return accepted
这里的 commonpath 比字符串前缀更可靠:/srv/app-data2 不会因为以 /srv/app-data 开头就被误判为子目录。目录成员也可以保留;如果业务只需要文件,可额外跳过 info.is_dir(),但不要因此忽略路径边界检查。
把白名单交给 extractall,而不是直接解压全部成员
检查通过后,建议解压到本次任务新建的临时目录,并用 members 参数传入白名单。这样代码意图清晰,也方便在异常时删除未完成目录。官方文档对 extractall 的警告仍然适用:不可信归档必须先检查,且路径安全不等于内容安全。
from tempfile import TemporaryDirectory
def extract_checked(zip_path: Path, output_root: Path) -> list[str]:
# 临时目录避免把半成品直接写入业务目录。
with TemporaryDirectory(prefix="zip-unpack-") as temp:
staging = Path(temp)
try:
with zipfile.ZipFile(zip_path) as zf:
members = safe_members(zf, staging)
if not members:
raise ValueError("没有通过路径检查的 ZIP 成员")
# 这里只解压白名单;资源上限应在此之前另行检查。
zf.extractall(staging, members=members)
except (zipfile.BadZipFile, OSError, ValueError) as exc:
raise ValueError(f"ZIP 解压失败:{exc}") from exc
output_root.mkdir(parents=True, exist_ok=True)
names = []
for item in staging.rglob("*"):
if item.is_file():
destination = output_root / item.relative_to(staging)
destination.parent.mkdir(parents=True, exist_ok=True)
item.replace(destination)
names.append(str(destination.relative_to(output_root)))
return names
示例把暂存目录中的文件移动到业务目录,实际项目还应根据是否允许覆盖、文件权限和并发任务决定采用原子替换、版本目录或拒绝同名文件。不要把“移动成功”写成绝对安全结论,它只说明这一次结果复查通过。
路径检查之外,还要限制 ZIP 的资源消耗
一个成员没有越界,并不代表归档可以放心展开。大量小文件、极高压缩比或超大未压缩内容都可能耗尽磁盘、inode 或处理时间。读取 ZipInfo.file_size 和成员数量后,可以在调用 extractall 前设置业务上限;同时检查暂存盘剩余空间,解压后再统计实际文件数和总大小。

def enforce_limits(members: list[zipfile.ZipInfo], max_files: int, max_bytes: int) -> None:
# 预先按未压缩大小估算资源占用,避免只看 ZIP 文件本身的大小。
if len(members) > max_files:
raise ValueError("ZIP 成员数量超过上限")
total = sum(max(0, info.file_size) for info in members)
if total > max_bytes:
raise ValueError("ZIP 展开总大小超过上限")
这组限制不能替代路径判断:一个只有几 KB 的归档也可能包含危险成员名,而路径全部安全的归档仍可能是资源耗尽风险。生产环境还要设置请求体上限、超时、磁盘配额和失败清理策略。
最后复查解压结果和常见误区
复查至少包括:所有输出路径都在目标根目录内、文件数量和总大小没有超过上限、关键文件存在且类型符合业务预期。不要使用 zipfile.Path 直接遍历不可信归档来代替检查,官方文档明确指出它不会自动清理成员文件名。也不要只过滤以 ../ 开头的字符串,因为分隔符、盘符和规范化后的路径都可能改变判断。
常见问题
extractall 已经会删除 ..,还需要白名单吗?需要。文档说明模块会尝试防止路径越界,但同时要求不可信归档先检查;白名单还能记录拒绝原因,并把业务允许的成员类型、数量和大小纳入同一入口。
检查了成员名,是否就能防住 ZIP 炸弹?不能。路径检查只回答“写到哪里”,资源限制回答“最多写多少”;两者必须分别配置,并在暂存目录完成结果复查。
把 infolist()、路径边界判断、资源上限和暂存目录组合起来,才能把 extractall() 放在一个可控的解压流程末端。对于不可信文件,宁可拒绝无法明确归类的成员,也不要依赖一次字符串替换或一次成功调用来证明安全。
AI画原型工具适合远程评审吗?用墨刀AI测试意见收集与版本闭环
- 上一篇
- AI画原型工具适合远程评审吗?用墨刀AI测试意见收集与版本闭环
- 下一篇
- Go hash.Hash Sum 如何追加摘要而不改变状态
-
- 文章 · python教程 | 4分钟前 | python · http.Client · HTTP连接 · Python http.Client HTTPResponse HTTPConnection
- Python http.client 读取响应后为何必须关闭连接
- 116浏览 收藏
-
- 文章 · python教程 | 3小时前 |
- Python multiprocessing forkserver 何时比 spawn 合适
- 271浏览 收藏
-
- 文章 · python教程 | 5小时前 |
- Python contextlib.AsyncExitStack 如何清理异步资源
- 369浏览 收藏
-
- 文章 · python教程 | 6小时前 |
- Python typing.TypeGuard 和 TypeIs 的收窄差异
- 295浏览 收藏
-
- 文章 · python教程 | 10小时前 | csv · python · DictWriter ·
- Python csv.DictWriter 缺少字段时如何设置 extrasaction
- 274浏览 收藏
-
- 文章 · python教程 | 11小时前 |
- Python pathlib Path.resolve 遇到不存在路径如何处理
- 278浏览 收藏
-
- 文章 · python教程 | 12小时前 |
- Python asyncio.timeout_at 如何用绝对截止时间包住任务
- 470浏览 收藏
-
- 文章 · python教程 | 15小时前 | 异常处理 · Python教程 · 兼容性 · ExceptionGroup · contextlib · ExceptionGroup Python contextlib.suppress BaseExceptionGroup except*
- Python contextlib.suppress 能否处理 ExceptionGroup 中的部分异常
- 431浏览 收藏
-
- 文章 · python教程 | 16小时前 | 资源管理 · python · memoryview · 缓冲区协议 · 性能编程 · Python memoryview memoryview.release Python 缓冲区协议 bytearray BufferError mmap 资源释放
- Python memoryview 使用后如何释放底层资源
- 275浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 40次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 135次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 72次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 29次使用
-
- CMMLU
- 深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
- 19次使用
-
- Go语言线程安全之互斥锁与读写锁
- 2022-12-27 346浏览
-
- 快速解决Golang Map 并发读写安全的问题
- 2022-12-29 443浏览
-
- 浅谈golang并发操作变量安全的问题
- 2023-01-07 251浏览
-
- go格式“占位符”输入输出 类似python的input
- 2023-01-19 346浏览
-
- Golang如何调用Python代码详解
- 2023-01-07 235浏览

