【漏洞分析】Node-tar Hardlink边界绕过问题深度分析
字数 4980
更新时间 2026-04-28 12:23:25

Node-tar Hardlink 路径穿越漏洞(CVE-2026-24842)深度教学

一、 漏洞概述

CVE编号: CVE-2026-24842
影响组件: Node-tar(Node.js下的TAR文件处理库)
影响版本: tar < 7.5.7
修复版本: tar@7.5.7
漏洞类型: Hardlink Path Traversal (硬链接路径穿越)
核心危害: 通过构造恶意TAR文件中的硬链接(hardlink)条目,使得在解压目录内创建的文件名,实际指向解压目录外的已存在文件,从而导致任意文件读取任意文件覆盖

二、 漏洞本质与攻击面演变

2.1 从传统路径穿越到对象绑定绕过的范式转变

传统归档提取漏洞(如Zip Slip)的攻击模式是路径字符串穿越。攻击者通过在压缩包中构造包含../的路径字符串(例如../../../etc/passwd),欺骗解压程序将文件直接写出到目标目录之外。其防御重点在于校验解压后的路径字符串是否仍在目标目录内

而CVE-2026-24842代表的是一种新的攻击范式:文件系统对象绑定绕过。攻击者利用归档中的特殊条目(如符号链接symlink、硬链接hardlink),使得最终被读取或写入的文件系统对象脱离预期边界,即使其路径字符串看起来完全位于目标目录内。

关键差异对比表

攻击类型 攻击原语 核心问题 主要影响
传统路径穿越 ../, 绝对路径 目标路径规范化不严,字符串校验绕过 直接越界写文件
符号链接绕过 symlink 提取时跟随符号链接,真实落点不在边界内 越界写文件/覆盖文件
硬链接绕过 hardlink 解压目录内对象与边界外已有对象指向同一inode 越界读写已有文件
链式链接 link 指向 link 校验与落地阶段使用的真实路径不一致 绕过单点校验

2.2 硬链接(Hardlink)的核心特性

理解本漏洞的前提是理解硬链接的工作机制:

  • 硬链接不是指向另一个路径的“快捷方式”(那是符号链接)。
  • 硬链接是文件系统层面的概念,它让多个不同的文件名指向同一个底层文件对象(inode)
  • 对其中任何一个文件名的读写操作,实际上都是在操作同一个文件。删除其中一个文件名,只要该inode的链接数(link count)不为0,文件数据就不会被删除。

三、 漏洞原理深度剖析

3.1 攻击流程总览

攻击者构造一个包含硬链接条目的恶意TAR文件,其核心结构如下:

  1. 一个目录条目:d/
  2. 一个硬链接条目:
    • entry.path (目标文件名): d/x
    • entry.linkpath (链接的源路径): ../secret.txt

当业务使用有漏洞的tar.extract()解压此归档到uploads/目录时,预期行为是创建uploads/d/x文件。但由于漏洞存在,实际行为是:在uploads/目录内创建了一个名为d/x硬链接,该硬链接指向了解压目录外的文件secret.txt

此后,当业务代码读取uploads/d/x时,实际读取的是secret.txt的内容;写入uploads/d/x时,实际修改的是secret.txt文件本身。

3.2 漏洞根因:校验与落地的语义分离

漏洞的根本原因在于Node-tar在处理硬链接条目时,校验阶段最终创建阶段所关注的路径对象不一致,导致安全边界被绕过。

以下是Node-tar(漏洞版本)处理硬链接条目的简化调用链及逻辑分析:

  1. [ONENTRY] -> [CHECKPATH] (路径检查):

    • 检查对象: 主要针对entry.path(即d/x)进行校验。
    • 检查逻辑: 检查entry.path是否包含..或是否为绝对路径。
    • 结果: 由于d/x是合法相对路径,检查通过。系统计算出entry.absolute/path/to/uploads/d/x,认为该路径仍在解压目录uploads/内。
  2. [CHECKFS] / [CHECKFS2] (文件系统检查):

    • 虽然entry.linkpath(即../secret.txt)被加入路径预约数组,但此阶段的主要目的是并发控制路径冲突避免并未entry.linkpath执行与entry.path同等的目录边界安全校验
    • 核心操作仍围绕entry.absoluteuploads/d/x)展开,如检查父目录存在性等。
  3. [MAKEFS] (文件创建分发):

    • 根据entry.type = Link,将处理逻辑分发到硬链接创建分支([HARDLINK]),不再按普通文件处理。
  4. [HARDLINK] (解析硬链接源路径) - 关键漏洞点:

    // 简化逻辑
    const linkpath = path.resolve(this.cwd, String(entry.linkpath));
    
    • 此时代码首次entry.linkpath解析为绝对路径。解析基于当前工作目录(this.cwd),即uploads/
    • 代入攻击输入:path.resolve(‘uploads/’, ‘../secret.txt’) 得到的是解压目录外的secret.txt的绝对路径。
    • 核心问题: 此前没有任何步骤对这个解析后的路径是否越界进行校验。
  5. [LINK] (创建硬链接):

    • 执行系统调用:fs.link(linkpath, entry.absolute)
    • 最终效果:fs.link(‘/absolute/path/secret.txt’, ‘/absolute/path/uploads/d/x’)
    • 结果:uploads/d/x../secret.txt 成为指向同一个inode的硬链接。

漏洞根因小结
校验阶段([CHECKPATH])只确保要创建的文件名entry.path)是安全的,但没有对该文件名将绑定到哪个文件对象entry.linkpath)进行同等强度的边界检查。在硬链接创建阶段([HARDLINK]),entry.linkpath被基于工作目录解析,可能指向目录外,而此解析结果未经验证即被用于创建链接,导致安全边界失效。

四、 漏洞复现与实践

4.1 实验环境搭建

  • 操作系统: macOS / Linux
  • Node.js: v23.11.0
  • npm: 10.9.2
  • 漏洞版本: tar@7.5.0
  • 修复版本: tar@7.5.7

目录结构初始化:

.
├── secret.txt          # 模拟解压目录外的敏感文件
├── uploads/            # 业务指定的安全解压目录
├── server.js           # 模拟业务的服务端代码
└── malicious.tar       # 攻击者构造的恶意TAR文件

4.2 构造恶意TAR文件

恶意TAR需包含一个硬链接条目,使其linkpath指向目录外的文件。
可使用以下Node.js脚本构造:

// create-malicious-tar.js
const tar = require('tar');
const fs = require('fs');
const path = require('path');

const cwd = process.cwd();
const tarPath = path.join(cwd, 'malicious.tar');

const pack = tar.c({
  gzip: false,
  cwd: cwd,
}, ['d/x']); // 要打包的条目,实际通过Stream注入元数据

const ws = fs.createWriteStream(tarPath);

// 手动构建TAR头部,创建指向../secret.txt的硬链接
// ... (具体实现涉及TAR格式字节操作,此处略去)
// 关键:设置 entry.type = 'Link' (硬链接类型), entry.path = 'd/x', entry.linkpath = '../secret.txt'

pack.pipe(ws);

4.3 模拟易受攻击的业务服务

一个简易的HTTP服务,模拟文件上传、解压及后续处理场景:

// server.js
const express = require('express');
const multer = require('multer');
const tar = require('tar'); // 版本 7.5.0
const fs = require('fs');
const path = require('path');

const app = express();
const UPLOAD_DIR = path.join(__dirname, 'uploads');

// 1. 上传并解压接口
app.post('/upload', multer().single('tarfile'), async (req, res) => {
  const tarPath = path.join(UPLOAD_DIR, 'upload.tar');
  fs.writeFileSync(tarPath, req.file.buffer);
  try {
    await tar.x({ // 使用有漏洞的tar.extract
      file: tarPath,
      cwd: UPLOAD_DIR, // 指定解压目录
    });
    res.send('Upload and extract OK');
  } catch (e) {
    res.status(500).send('Extract failed');
  }
});

// 2. 读取解压文件接口(模拟业务读取)
app.get('/read', (req, res) => {
  const filePath = path.join(UPLOAD_DIR, 'd', 'x'); // 业务认为的安全路径
  try {
    const data = fs.readFileSync(filePath, 'utf8');
    res.send(data);
  } catch (e) {
    res.status(500).send('Read failed');
  }
});

// 3. 写入解压文件接口(模拟业务处理写入,如生成缓存、追加元数据)
app.post('/write', express.text(), (req, res) => {
  const filePath = path.join(UPLOAD_DIR, 'd', 'x');
  try {
    fs.appendFileSync(filePath, `\n${req.body}`);
    res.send('Write OK');
  } catch (e) {
    res.status(500).send('Write failed');
  }
});

app.listen(3000);

服务逻辑说明

  • /upload: 接收TAR文件,使用tar.extract()解压到uploads/目录。业务预期所有文件均被限制在该目录内。
  • /read: 模拟业务读取解压后的文件uploads/d/x
  • /write: 模拟业务对解压后的文件进行写入操作(如追加内容)。

4.4 漏洞验证步骤

  1. 启动服务node server.js
  2. 上传恶意TAR
    curl -F ‘tarfile=@malicious.tar’ http://localhost:3000/upload
    
  3. 验证越界读取
    curl http://localhost:3000/read
    
    预期结果(漏洞版本):返回secret.txt的内容,尽管业务代码读取的是uploads/d/x
  4. 验证越界覆盖
    curl -X POST -d “HACKED” http://localhost:3000/write
    
    预期结果(漏洞版本)secret.txt文件末尾被添加了“HACKED”字符串。
  5. 验证inode绑定
    ls -li uploads/d/x secret.txt
    
    预期结果:两个文件显示相同的inode编号和link count为2,证实它们是同一个文件的硬链接。

4.5 修复版本验证

  1. 升级到修复版本:npm install tar@7.5.7
  2. 重复上述攻击步骤。
  3. 预期结果:解压失败或无法创建uploads/d/x文件,/read接口返回“No such file or directory”,secret.txt内容未被读取或覆盖。

五、 修复方案分析

在修复版本tar@7.5.7中,漏洞在[CHECKPATH]阶段被阻断。

修复核心:在路径检查阶段,不仅检查entry.path对硬链接条目的entry.linkpath进行严格的边界校验。

关键代码逻辑(简化)

  1. [CHECKPATH]中,对于类型为Link的条目,会额外调用[STRIPABSOLUTEPATH](entry, 'linkpath')entry.linkpath进行校验。
  2. [STRIPABSOLUTEPATH]函数会检查目标字段(此处是linkpath)是否包含..。如果包含,则直接抛出TAR_ENTRY_ERROR并中止该条目的处理。

效果:攻击者提供的entry.linkpath = ‘../secret.txt’在解压流程的早期就被识别并拒绝,硬链接根本不会被创建,攻击链被彻底阻断。

六、 防御建议与最佳实践

  1. 升级与补丁

    • 立即升级node-tar到7.5.7或更高版本。
  2. 业务层防御

    • 默认拒绝链接:如果业务场景不需要符号链接或硬链接,应在解压前过滤或拒绝包含此类条目的归档文件。
    • 独立校验linkpath:对于允许链接的业务,必须对entry.linkpath进行独立的、与entry.path同等级别的路径边界校验,确保其解析后的绝对路径仍在目标目录内。
    • 解压目录隔离:将解压目录设置为独立的、无敏感文件的临时空间或沙箱环境。
    • 最小权限原则:运行解压过程的进程应使用最低必要的文件系统权限,以限制越界访问可能造成的损害。
  3. 安全设计原则

    • 从字符串校验转向对象边界校验:安全设计的重点应从“路径字符串是否包含..”转变为“最终被操作的文件系统对象是否在可信边界内”。
    • 语义一致性:确保在归档处理的整个链路中,对路径的解析、校验和最终使用的语义保持一致,避免出现校验一个路径、使用另一个路径的情况。

七、 同类漏洞案例(2026年)

该漏洞模式并非孤例,2026年出现的多个归档提取漏洞均与链接处理相关:

项目 时间 CVE编号 机制 影响概括
Node-tar 2026-01-28 CVE-2026-24842 hardlink 目标路径校验不足 创建指向外部文件的 hardlink,造成文件读写/覆盖
compressing 2026-02-04 CVE-2026-24884 symlink 目标未充分校验 后续文件经 symlink 写入任意位置
Zarf 2026-03-06 CVE-2026-29064 symlink 指向目标目录外 造成任意文件读写
Jenkins 2026-03-18 CVE-2026-33001 .tar/.tar.gz 提取时未安全处理 symlink 可向任意位置写文件

共同模式:路径边界 != 对象边界。业务逻辑认为在安全目录内操作,但文件系统通过链接解析,将实际操作对象绑定到了目录之外。

八、 总结

CVE-2026-24842揭示了归档提取类漏洞的一个演进方向:攻击面从简单的路径字符串规范化问题,转向更底层的文件系统对象绑定问题。防御思路需要同步升级,从检查“路径是否安全”,进阶到确保“最终操作的对象是否安全”。对于处理用户上传归档文件的业务,必须对符号链接和硬链接等特殊文件类型保持警惕,并在校验逻辑中给予同等重视。

相似文章
相似文章
 全屏