{"id":"CVE-2026-46395","aliases":[],"url":"https://o3.security/vulnerability/CVE-2026-46395","summary":"HAXcms: Private Key Disclosure via Broken HMAC Implementation","details":"### Summary\nThe `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.\n\n### Details\nBug 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.\n\nBug 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.\n\nEvery 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.\n\nThe `/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.\n\nThe 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.\n\n### PoC\n1. GET request to `/system/api/connectionSettings` endpoint and fetch the token.\n2. 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).\n3. 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)}\n4. The same key can also be used to create other tokens (user_token, base_token, form_token, etc).\n5. Use these forged tokens to hit all authenticated endpoints (modify/delete/create etc) with admin privileges.\n\n### Impact\nAn unauthenticated attacker can perform the complete attack chain with a single HTTP request:\n1. Extract private key: GET \"/system/api/connectionSettings\", base64-decode any token, discard first 32 bytes.\n2. Forge admin JWT: sign arbitrary JWT payloads with the stolen privateKey+salt.\n3. Forge all request tokens: compute valid user_token, site_token for any API call.\n4. Full admin access: create/modify/delete sites, upload files, modify content.\n\nThis works even if the admin has changed the default credentials to a strong password. The forged tokens produce no login events in logs.","published":"2026-05-19T14:44:55Z","modified":"2026-06-09T12:00:13.426465726Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"@haxtheweb/haxcms-nodejs","fixedVersion":"26.0.0"}],"fix":null,"references":[{"type":"WEB","url":"https://github.com/haxtheweb/issues/security/advisories/GHSA-6c8g-9hfh-pq5h"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-46395"},{"type":"PACKAGE","url":"https://github.com/haxtheweb/issues"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-06-09T12:00:13.426465726Z"}}