GHSA-6c8g-9hfh-pq5hCritical

HAXcms: Private Key Disclosure via Broken HMAC Implementation

Published
May 19, 2026
Last Modified
May 19, 2026

🔗 CVE IDs covered (1)

📋 Description

### Summary The `hmacBase64()` function in the HAXcms Node.js backend contains two critical cryptographic implementation errors that together allow any unauthenticated attacker to extract the system’s private signing key and forge arbitrary admin-level JSON Web Tokens (JWTs) allowing them to get full admin access with a single HTTP request. ### Details Bug 1: Hardcoded HMAC Key (line 2160): The function passes the literal string "0" as the HMAC signing key instead of the key parameter, making every HAXcms instance compute identical HMACs for the same input. Bug 2: Private Key Appended to Output (lines 2161- 2163): After computing the HMAC, the function concatenates the real key parameter which is "this.privateKey + this.salt", the system’s master signing secret is directly onto the output. The combined buffer is base64-encoded and returned as the token. Every base64url token produced has the same structure: 32 bytes HMAC keyed with "0" and N bytes of `privateKey+salt`. An attacker base64-decodes any token, discards the first 32 bytes, and reads the private key directly. The `/system/api/connectionSettings` endpoint is unauthenticated and returns multiple tokens generated by this function. A single GET request to this endpoint exposes the private key. The PHP backend (HAXCMS.php:1619-1631) implements this function correctly with the actual key and returns only the hash. The PHP version produces 44-character tokens whereas the broken Node.js version produces 139+ character tokens. ### PoC 1. GET request to `/system/api/connectionSettings` endpoint and fetch the token. 2. Extract the private key from the fetched token. The `hmacBase64()` function produces 32 bytes with HMAC-SHA256 with hardcoded key "0" and the rest of the bytes are `privateKey+salt` (plaintext). Decode the Base64 token, discard the first 32 bytes, read the remaining bytes as UTF-8 (this is your extracted private key). 3. Since JWT's are signed with `privateKey+salt`, use this stolen private key to forge a JWT for admin using `JWT.sign(payload, this.privateKey+this.salt)`. NOTE: the payload uses {id, user (set this as admin), iat (current timestamp), exp (expiration timestamp)} 4. The same key can also be used to create other tokens (user_token, base_token, form_token, etc). 5. Use these forged tokens to hit all authenticated endpoints (modify/delete/create etc) with admin privileges. ### Impact An unauthenticated attacker can perform the complete attack chain with a single HTTP request: 1. Extract private key: GET "/system/api/connectionSettings", base64-decode any token, discard first 32 bytes. 2. Forge admin JWT: sign arbitrary JWT payloads with the stolen privateKey+salt. 3. Forge all request tokens: compute valid user_token, site_token for any API call. 4. Full admin access: create/modify/delete sites, upload files, modify content. This works even if the admin has changed the default credentials to a strong password. The forged tokens produce no login events in logs.

🎯 Affected products1

  • npm/@haxtheweb/haxcms-nodejs:<= 25.0.0

🔗 References (2)