Hardcoded RSA private key used for JWT signing
A complete RSA private key is embedded as a string literal and used to sign JWTs and derive the deluxe-token HMAC.
At line 22, privateKey is assigned an inline -----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY----- PEM block:
const privateKey = '-----BEGIN RSA PRIVATE KEY-----\r\nMIICXAIBAAKBgQDNwqLEe9wgTXCbC7+RPdDbBbeqjdbs4kOPOIGzqLpXvJXlxxW8iMz0EaM4BKUqYsIa+ndv3NAn2RxCd5ubVdJJcX43zO6Ko0TFEZx/65gY3BE0O6syCEmUP4qbSd6exou/F+WTISzbQ5FBVPVmhnYhG/kpwt/cIxK5iUn5hm+4tQIDAQAB...-----END RSA PRIVATE KEY-----'
This key is then used in two security-critical sinks:
authorize():jwt.sign(user, privateKey, { expiresIn: '6h', algorithm: 'RS256' })— every authenticated session token is signed with this key.deluxeToken():crypto.createHmac('sha256', privateKey)— used to mint the role-elevation token verified inisDeluxe().
Because the key lives in source, anyone with read access to the repository (or the published artifact) can forge arbitrary JWTs (including admin tokens) and arbitrary deluxe-role tokens. The matching publicKey is loaded from encryptionkeys/jwt.pub, confirming this is the real signing key for the deployed app, not a placeholder. The private key should be stored in a secret manager or environment variable and loaded at runtime, never committed.
- Copy the PEM block from line 22 into a local file
priv.pem. - Forge an admin JWT:
const jwt = require('jsonwebtoken');
const fs = require('fs');
const token = jwt.sign({ data: { id: 1, email: 'admin@juice-sh.op', role: 'admin' } }, fs.readFileSync('priv.pem'), { algorithm: 'RS256', expiresIn: '6h' });
console.log(token);
- Submit the token in the
Authorization: Bearer ...header — the server accepts it because the matchingjwt.pubvalidates it. - Bonus: compute
HMAC-SHA256(privateKey, email + 'deluxe')to forge a deluxe token and bypass theisDeluxe()check.
Unauthenticated attackers who view the repo (open-source) can forge JWTs for any user and any role, achieving full authentication bypass and privilege escalation to admin. They can also forge deluxe-membership tokens, bypassing paid-tier checks. Rotation requires replacing the key, redeploying, and invalidating every issued token.
Line 22 literally contains a full PEM-encoded RSA private key assigned to privateKey, which is then used by authorize() via jwt.sign(user, privateKey, { algorithm: 'RS256' }) and by deluxeToken() via crypto.createHmac('sha256', privateKey). The matching publicKey is loaded from encryptionkeys/jwt.pub and used by isAuthorized/verify/updateAuthenticatedUsers, so anyone with repo access can forge admin JWTs and deluxe HMACs that the server will accept. The scope explicitly instructs to treat the finding on production merit and not dismiss it due to Juice Shop being a training app. The detector's PoC (sign an admin claim with the leaked PEM and submit as Bearer token) is directly reachable.
The RSA private key is hardcoded at line 22 and used by authorize() to sign every JWT with RS256, while the matching public key at encryptionkeys/jwt.pub is used by isAuthorized()/verify() to validate tokens — so anyone reading the open-source repo can forge a JWT with role: 'admin' and any user id, plus forge the deluxeToken() HMAC (also keyed on privateKey) to satisfy isDeluxe(). Exploitation is fully remote over HTTP via the Authorization header, requires no prior authentication, no user interaction, and no special preconditions beyond copying the embedded PEM. Forged admin tokens grant total compromise of confidentiality (read any user/order data), integrity (modify any record via admin endpoints), and availability (admin can delete/disrupt data), all within the application's own authority so scope is Unchanged.
- CWE-798
- CWE-321
- CWE-259
- OWASP A07:2021 - Identification and Authentication Failures