Python tarfile 解包怎样限制链接路径
来源:17golang原创
时间:2026-11-03 12:18:22 196浏览 收藏
如果 Python 程序要解包来源不完全可信的 tar 文件,建议在 Python 3.12 及以上显式传入 filter="data",再用一个自定义过滤器把符号链接和硬链接全部跳过。这样既能限制绝对路径、目标目录外路径和特殊文件,也能把“链接是否允许”变成明确的业务策略,而不是依赖运行环境的默认值。
官方文档:https://docs.python.org/3/library/tarfile.html
只检查成员名称里的 .. 还不够。链接的目标、目标目录的真实路径和特殊文件类型都要纳入解包策略;发生过滤异常后,目标目录可能只解出了一部分内容,不能把它当成完整结果继续使用。
- Python 3.12 引入
extract()和extractall()的filter参数。 filter="data"会拒绝绝对路径、越界链接和设备文件,并限制部分元数据。- 如果业务根本不需要链接,额外返回
None跳过符号链接和硬链接更直观。 - Python 3.14 起默认过滤器改为
data,但显式写出策略有助于兼容 3.12、3.13 和代码审查。
先区分“路径越界”和“链接不允许”
tar 成员至少有两层需要处理:成员自身的名字,以及符号链接或硬链接的目标。成员名字为绝对路径、规范化后落到目标目录之外,属于路径边界问题;链接目标可以把一个看似位于解包目录里的条目指向别处,属于链接边界问题。两者不是同一个判断,不能只用字符串前缀或只查找 ../。
Python 官方的 data 过滤器已经覆盖常见的数据包场景:拒绝绝对路径、拒绝解析后落到目标目录外的链接、拒绝设备文件,并收紧权限和所有者元数据。若业务需要保留内部软链接,应先使用这一层,再在自定义过滤器中允许明确的例外。

先显式使用 data 过滤器
对于普通数据包、上传附件或构建产物,最小改动是给 extractall() 明确传入 filter="data"。这不会把 tar 解包变成完整的恶意文件检测器,但能把标准库已经定义的路径、链接和特殊文件限制应用到当前调用点。
from pathlib import Path
import tarfile
archive_path = Path("incoming/package.tar.gz")
target_dir = Path("work/unpacked")
target_dir.mkdir(parents=True, exist_ok=True)
try:
with tarfile.open(archive_path, mode="r:*") as archive:
# 显式选择数据包策略,避免依赖不同 Python 版本的默认值。
archive.extractall(path=target_dir, filter="data")
except tarfile.FilterError as exc:
# 过滤失败后目录可能只有部分内容,后续流程不要继续读取它。
print(f"拒绝解包成员:{exc}")
raise
这里的 r:* 允许按归档头自动识别常见压缩格式;真正决定链接和路径边界的是 filter 参数。FilterError 表示成员被策略拒绝,异常发生后应删除或隔离 target_dir,再由上层决定是否重试。
不需要链接时,用自定义过滤器全部跳过
如果解出来的是源码包、数据交换包或临时构建输入,通常不需要保留软链接和硬链接。可以先调用官方 data_filter(),再对返回的 TarInfo 检查类型;返回 None 表示跳过当前成员。这样不会重新实现路径规范化和越界判断。
from pathlib import Path
import shutil
import tarfile
def regular_files_only(member: tarfile.TarInfo, destination: str):
# 先复用官方规则,处理绝对路径、越界链接和特殊文件。
safe_member = tarfile.data_filter(member, destination)
if safe_member is None:
return None
# 当前业务只接收目录和普通文件,不保留软链接或硬链接。
if safe_member.issym() or safe_member.islnk():
return None
return safe_member
archive_path = Path("incoming/data.tar")
target_dir = Path("work/data")
if target_dir.exists():
# 清掉上一次失败留下的半成品,避免新旧文件混在一起。
shutil.rmtree(target_dir)
target_dir.mkdir(parents=True)
try:
with tarfile.open(archive_path, mode="r:*") as archive:
# 自定义过滤器只增加“禁用链接”这一条业务规则。
archive.extractall(target_dir, filter=regular_files_only)
except tarfile.FilterError as exc:
# 解包失败时不把半成品目录交给后续业务。
shutil.rmtree(target_dir, ignore_errors=True)
raise RuntimeError("tar 包未通过解包策略") from exc
过滤器接收当前成员和目标目录路径,每个成员解包前都会调用。data_filter() 可能直接抛出 FilterError,而 issym() 与 islnk() 则负责表达“本业务不接受任何链接”。如果确实需要内部链接,不要简单删除这两个判断,而应给允许的目标建立更窄的规则。

链接路径限制应放在解包前的过滤点
extractall() 的过滤器是在每个成员真正落盘前调用的,适合做“允许、修改或拒绝”决策。相比先调用 getmembers() 扫一遍再解包,过滤点能在当前文件系统状态下处理成员,也不会把同一组 TarInfo 对象的修改意外带到后续操作。
但过滤器不是资源配额系统:它不会自动限制归档展开后的总字节数、文件数量、单文件大小或压缩炸弹风险。面对外部上传包,还要在业务层设置上传大小、成员数量、总解包预算,并把解包放到权限较低的隔离目录中。
兼容 Python 版本并处理半成品
filter 参数从 Python 3.12 开始提供,Python 3.14 起默认值改为 data。如果项目仍支持 3.11 或更早版本,直接传入 filter 会触发参数不兼容;更稳妥的做法是升级运行时,或者在旧版本使用经过充分审查的独立解包实现,不要假装旧版本拥有同样的标准库策略。
异常恢复也要单独设计。过滤器拒绝成员时,归档可能已经解出部分文件;成功路径应在全部解包完成后再把临时目录原子改名,失败路径删除临时目录,避免消费者读到半成品。
最后按业务需要选策略
| 场景 | 建议 | 原因 |
|---|---|---|
| 普通数据包 | filter="data" | 直接采用标准库的数据包边界 |
| 不需要任何链接 | data_filter() 加 issym()/islnk() | 把链接能力明确关闭 |
| 可信备份恢复 | 只在来源和权限都可控时评估 fully_trusted | 保留 tar 元数据的代价更高 |
| 旧 Python 运行时 | 优先升级,或使用独立审查过的解包层 | 旧版本没有同一套 filter API |
一句话总结:限制链接路径的入口是 extractall(filter=...),基础规则交给 data 过滤器,业务不需要链接时再返回 None 跳过软链接和硬链接;最后用临时目录、权限和资源配额补上标准库过滤器不负责的部分。
常见问题
filter="data" 会不会保证所有 tar 包都安全?
不会。它主要限制路径越界、危险链接和特殊文件,并收紧部分元数据;文件数量、展开总大小、业务内容和运行权限仍要由应用处理。
为什么不只判断成员名是否包含 ..?
因为绝对路径、规范化后的越界路径和链接目标是不同情况。字符串判断容易漏掉组合路径,也无法表达链接是否允许。
过滤器返回 None 会报错吗?
不会。对自定义 extraction filter 来说,返回 None 表示跳过当前成员;官方过滤器拒绝危险成员时通常会抛出 FilterError。
Python 3.11 如何复用这套写法?
3.11 没有 extraction filter 参数和同一套内置过滤器。应优先升级到支持该 API 的版本;如果必须兼容旧版本,要单独采用经过审查的解包层,并把版本差异写进部署约束。
-
373 收藏
-
152 收藏
-
文章 · python教程 | 1小时前 | 标准库 · xml · Python教程 · Python xml 流式解析 ElementTree xml.etree.ElementTree iterparse339 收藏
-
424 收藏
-
文章 · python教程 | 1小时前 | SQLite · 事务管理 · Python教程 · Python 事务 上下文管理器 sqlite3 autocommit isolation_level Connection257 收藏
-
177 收藏
-
184 收藏
-
237 收藏
-
480 收藏
-
218 收藏
-
文章 · python教程 | 1小时前 | 格式化 · Template Python 3.14 t-string string.templatelib template strings Interpolation418 收藏
-
276 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习