导语:ExcelJS 任意文件读取漏洞(CVE-2026-128347)是近期 Node.js 生态中一个 CVSS 高达 8.7 的高危供应链风险——攻击者只需向流式 WorkbookWriter 的 addImage 传入带
../的文件名,就能把服务器上任意可读文件悄悄嵌入生成的 Excel 并下载回本地。作为长期负责服务端导出链路的架构师,我认为这不是一次普通的依赖升级,而是一次必须按应急响应流程闭环的生产安全事件。
一、ExcelJS 任意文件读取漏洞的事件全貌与影响面
根据 NVD 官方通告,CVE-2026-128347 影响 ExcelJS 4.4.0 及之前的所有版本,漏洞位于流式写入器 WorkbookWriter 的图片处理链路。核心问题在于:addImage 接受用户可控的图片文件名,而底层 addMedia 在把媒体写入 ZIP 包时,未对文件名做规范化与目录逃逸校验,导致 ../ 路径段可以穿透到进程可访问的任意目录。
从架构视角看,这类漏洞的危险性被三个因素放大:其一,Excel 导出几乎是所有后台管理系统的标配能力;其二,流式写入常用于大报表,往往直接暴露在对外 API 上;其三,Node.js 进程通常以应用账号运行,可读取 .env、私钥、配置文件等敏感资产。换句话说,一个看似无害的“导出带图报表”接口,可能变成任意文件下载通道。
只要你的服务存在“用户可影响 addImage 文件名”的调用路径,且导出接口对公网可达,就应视为已暴露。攻击者无需认证绕过、无需 RCE,仅凭一次导出请求即可完成数据外带,隐蔽性极强。
企业级架构规范
flowchart TD
subgraph 接入层 ["表现层与接入网关 (Client & Gateway Tier)"]
A["攻击者构造导出请求 (imageName=../../etc/passwd)"] --> B["业务导出 API (未做文件名白名单校验)"]
end
subgraph 应用层 ["应用服务与调用层 (Application Tier)"]
B --> C["ExcelJS WorkbookWriter.addImage(filename)"]
C --> D["addMedia 拼接 xl/media 目标路径"]
end
subgraph 核心层 ["文件系统与路径解析 (Core FS Tier)"]
D --> E["path 未规范化, ../ 逃逸出媒体目录"]
E --> F["读取进程可访问的任意本地文件"]
end
subgraph 输出层 ["数据外带与持久化 (Exfiltration Tier)"]
F --> G["文件内容写入 xl/media 并打包 xlsx"]
G --> H["响应下载, 敏感数据外带"]
end
二、漏洞根因拆解:addImage 到 addMedia 的路径穿越
2.1 调用链与缺陷点定位
ExcelJS 的流式写入模型下,图片资源先经 addImage 登记,再由 addMedia 在打包阶段读取并写入 xl/media 目录。缺陷点在于:addMedia 直接使用传入的 filename 作为读取源,同时用同一字符串参与目标路径拼接,既没有 path.basename() 截断目录,也没有 path.resolve() 后做前缀白名单校验。
const ExcelJS = require('exceljs');
async function exportReport(userControlledImageName) {
const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({ filename: 'out.xlsx' });
const sheet = workbook.addWorksheet('report');
// 危险点:文件名完全来自用户输入
const imageId = workbook.addImage({
filename: userControlledImageName, // 例如 '../../../../etc/passwd'
extension: 'png'
});
sheet.addImage(imageId, 'B2:D6');
await workbook.commit();
}
2.2 为什么流式写入器风险更高
- 延迟读取:流式模式下媒体文件在 commit 阶段才真正读取,异常堆栈与业务日志脱节,攻击痕迹更难被发现。
- 大文件场景:流式导出常用于百万行报表,接口往往放宽超时与鉴权,攻击窗口更大。
- 路径拼接惯性:开发者习惯认为“图片名只是资源标识”,忽视了它同时是文件系统路径的一部分。
任何“用户可控字符串参与文件系统路径拼接”的位置,都必须经过 path.resolve 归一化 + 前缀白名单双重校验。这不是 ExcelJS 独有的问题,而是所有文件处理库的通用防御基线。
三、影响面排查:你的服务是否在射程内
应急响应的第一步不是升级,而是精准定位暴露面。建议按以下清单在代码仓库与运行时两个维度同时排查。
| 排查维度 | 高危特征 | 安全特征 | 处置建议 |
|---|---|---|---|
| 依赖版本 | exceljs <= 4.4.0 | 已升级至修复版本 | 立即升级 |
| 调用方式 | addImage 的 filename 来自请求参数 | filename 为服务端常量或白名单 ID | 改为 ID 映射 |
| 接口暴露 | 导出接口公网可达、弱鉴权 | 内网 + 强鉴权 + 限流 | 收敛暴露面 |
| 进程权限 | 以 root 或高权限运行 | 最小权限专用账号 | 降权运行 |
在运行时侧,可通过依赖扫描快速定位:
# 查看当前锁定版本 npm ls exceljs # 使用 npm audit 检查已知漏洞 npm audit --json | grep -i exceljs # 全局检索 addImage 调用点 grep -rn "addImage" ./src --include=*.js --include=*.ts
不要只检查直接依赖。ExcelJS 常被报表中间件、低代码平台、导出 SDK 间接引入,务必对 lock 文件做全量传递依赖扫描,避免“以为没用到”的漏网之鱼。
四、应急修复方案:从临时缓解到彻底闭环
4.1 临时缓解(无法立即升级时)
若因兼容性暂时无法升级,必须在业务层做输入净化,核心是“只允许安全字符 + 强制 basename + 前缀校验”三重防御。
const path = require('path');
const MEDIA_ROOT = path.resolve('/srv/app/uploads/images');
function safeImagePath(userInput) {
// 1. 只保留文件名部分,剥离任何目录段
const base = path.basename(userInput);
// 2. 白名单字符校验
if (!/^[A-Za-z0-9._-]+.(png|jpg|jpeg|gif)$/i.test(base)) {
throw new Error('invalid image name');
}
// 3. 归一化后做前缀白名单
const resolved = path.resolve(MEDIA_ROOT, base);
if (!resolved.startsWith(MEDIA_ROOT + path.sep)) {
throw new Error('path traversal detected');
}
return resolved;
}
module.exports = { safeImagePath };
4.2 彻底修复:升级与架构收敛
根治方案是升级到官方修复版本,并同步完成两项架构收敛:一是把“文件名”从外部输入改为服务端资源 ID 映射;二是将导出进程降权运行,切断高价值文件的读取可能。
点击展开:升级执行命令与回归验证脚本
# 1. 升级 ExcelJS 至修复版本
npm install exceljs@latest --save-exact
# 2. 锁定并审计
npm audit fix
npm ls exceljs
# 3. 回归:验证路径穿越已被拦截
node -e "
const ExcelJS = require('exceljs');
const wb = new ExcelJS.stream.xlsx.WorkbookWriter({ filename: '/tmp/t.xlsx' });
try {
wb.addImage({ filename: '../../etc/passwd', extension: 'png' });
console.log('STILL VULNERABLE');
} catch (e) {
console.log('BLOCKED:', e.message);
}
"
五、架构级防御:把路径穿越挡在业务之外
单点修复只能解决一次 CVE,体系化防御才能避免下一次。结合本次 ExcelJS 任意文件读取漏洞的教训,我建议在架构层固化三条基线:
- 输入即不可信:所有参与文件路径拼接的外部字符串,统一走安全工具函数,禁止裸用。
- 资源 ID 化:对外只暴露资源 ID,服务端通过映射表定位真实文件,从源头消灭路径注入面。
- 最小权限运行:导出、转换、缩略图等文件密集型任务独立进程、独立账号、独立目录,限制可读范围。
此外,建议把 npm audit 与 SCA 扫描纳入 CI 门禁,对 CVSS ≥ 7.0 的漏洞阻断合并;对导出类接口增加异常文件名告警规则,一旦出现 ../ 或绝对路径特征立即触发安全事件。
安全修复的优先级应是“先止血、再根治、后加固”。本次事件中,输入净化是止血,升级是根治,资源 ID 化与进程降权是加固,三者缺一不可。
