Chartbrew: Full Account Takeover via Default Cryptographic Key
Chartbrew's authentication accepts JWTs signed with a hard-coded, publicly known default key. An unauthenticated attacker forges a token for any account and takes over that user's full session, bypassing password and two-factor authentication.
Advisory ID: TP-2026-054
Product: Chartbrew (open-source business-intelligence platform for data visualization, self-hosted)
Vulnerability type: Use of Default Cryptographic Key (CWE-1394)
CVE: CVE-2026-85295
CVSS 3.1: 9.8 (Critical) · CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Vendor advisory: GHSA-qvv4-r3q6-8797
Affected versions: <= 5.2.2
Fixed in: Chartbrew 5.2.3
Reported: 16 July 2026
Summary
Chartbrew is a self-hosted open-source business-intelligence platform for data visualization whose server authenticates every API and WebSocket request by verifying the bearer JWT against two keys: first CB_ENCRYPTION_KEY, then CB_SECRET. CB_SECRET ships in .env-template as the fixed value change_to_random_string, is commented "[deprecated]", and is never generated at boot. Four auth middlewares (verifyToken.js:26, verifyUser.js:27, getUserFromToken.js:22, socketManager.js:43) fall back to jwt.verify(token, settings.secret) with no algorithms allow-list. An unauthenticated attacker forges an HS256 JWT for any user id and holds that user's full session, bypassing password and 2FA.
Root cause
Four auth middlewares fall back to jwt.verify(token, settings.secret) (= CB_SECRET) with no algorithms allow-list after the primary CB_ENCRYPTION_KEY verification fails (server/modules/verifyToken.js:26, verifyUser.js:27, getUserFromToken.js:22, socketManager.js:43). settings.secret maps to process.env.CB_SECRET (server/settings.js:3), while user sessions are signed only with settings.encryptionKey (server/api/UserRoute.js:25), so the CB_SECRET path is a second acceptance key no legitimate token uses. CB_SECRET ships as the static placeholder change_to_random_string in .env-template:32 under the comment "[deprecated] This is not used anymore". setUpEncryptionKeys.js generates CB_ENCRYPTION_KEY when empty but never touches CB_SECRET, so operators leave the default in place.
Proof of Concept
# Forge an HS256 JWT for victim user id 2, signed with the known CB_SECRET:
HEADER {"alg":"HS256","typ":"JWT"}
PAYLOAD {"id":2}
KEY change_to_random_string
# Present it to any authenticated route:
GET /user/2 HTTP/1.1
Authorization: Bearer <forged>
HTTP/1.1 200 OK
{"id":2,"email":"[email protected]","name":"Victim", ...}
# Mint a fresh, app-issued session for the victim:
POST /user/relog HTTP/1.1
Authorization: Bearer <forged>
HTTP/1.1 200 OK
{"id":2,"email":"[email protected]", ..., "token":"<fresh victim session>"}
Impact
- Full session as any user id (sequential integers from 1): read, write, delete of that user's data.
- Password and 2FA bypassed: the forge never touches the login path.
- Reaches instance admin if any user has admin=1.
- Anonymous, single request, no user interaction, on default config.
References
Is Something Like This in Your Software?
Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.
