python 3.12中tarfile.extractall()默认不拦截../路径,仍存在路径遍历风险;需依赖filter="data"(默认启用)、规范目标路径os.path.realpath()、并校验每个tarinfo.name是否为安全相对路径。

tarfile.extractall() 默认不拦截 ../ 路径
Python 的 tarfile 模块从设计上就**不自动阻止路径遍历**——哪怕你用的是 Python 3.12,extractall() 依然会把 ../../../etc/passwd 这样的成员名原样写入文件系统,只要目标目录可写。这不是 bug,是历史包袱:早期开发者认为“信任输入源”是使用者的责任,所以没做默认防护。
常见错误是直接写:tarfile.open('a.tar.gz').extractall(),结果恶意包里一个 ./sub/../etc/shadow 成员就被解到根目录下。
-
filter="data"在 3.11+ 是默认启用的,但它只在extract()和extractall()内部生效;如果你手动遍历getmembers()并调用extract()却没传filter,它就失效 - 显式设
filter=None或绕过filter逻辑(比如用members参数传入未过滤列表),等于主动关掉防护 - Windows 上
..\同样有效,且某些解析器还会把%2e%2e%5c(URL 编码的..\)还原后执行
os.path.join 无法防御绝对路径和 ../
os.path.join(base_dir, member.name) 看似安全,实则陷阱重重:一旦 member.name 以 / 开头(如 /tmp/malware.py),join 会直接返回该绝对路径;如果含 ../(如 ../../bin/sh),拼接后仍可能逃逸出 base_dir 边界。
真正可靠的做法是先规范化再校验:
- 用
os.path.normpath(member.name)把路径标准化(合并../、./) - 检查结果是否以
'..'开头或包含'/..'—— 注意不能只搜字符串'..',否则my..txt会被误杀 - 再确认它是不是相对路径:
not os.path.isabs(normed_name)
Bandit B202 能提前发现不安全调用
静态扫描工具 bandit 的 B202 插件专盯 tarfile.extractall() 的裸调用。它把以下写法标为高风险:
-
tar.extractall()(无参数) -
tar.extractall(path=dest)但dest不是规范后的绝对路径(比如用了os.getcwd()或用户输入) -
members参数来源不可信(如来自变量、配置、请求参数)
它不会报错,但能提醒你:这里必须加白名单过滤或 filter 显式声明,否则就是生产环境雷区。
真正安全的解压流程只有三步
别信“加个字符串过滤就行”。真实防御要分层落地:
- 打开时指定模式:
tarfile.open('x.tar.gz', mode='r:gz'),避免因模式错误导致底层读取失败却掩盖路径问题 - 解压前遍历
getmembers(),对每个TarInfo实例做两件事:os.path.normpath(tarinfo.name).startswith(('..', '/'))→ 拒绝;tarinfo.isfile() and not tarinfo.islnk()→ 接受(跳过链接、设备文件) - 目标目录必须是规范后的绝对路径:
os.path.realpath('/safe/dest'),且确保其父目录不可被遍历者写入
漏掉任何一环,比如忘了 realpath 或放行了 islnk(),攻击者就能用符号链接覆盖 /etc/crontab 或写入 Web 目录执行 shell。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











