[SECURITY AUDIT] SSO flow token (sso_tx) validates as a session — token-type confusion in sessionFromDecoded #32
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: Medium
Confidence: High (mechanism confirmed by reading the code); exploit impact is bounded (see below)
Scope area: Session management (audit area 4), auth transition overlap (area 2)
What
All JWTs in the app are signed with the same
JWT_SECRETand are distinguished only by claims.sessionFromDecodedinserver/src/auth/session.service.js:37-48rejects a token as "not a session" only whendecoded.stageis present:The TOTP challenge token carries
stage:'totp'and is correctly rejected. But the SSO transaction cookie token (server/src/auth/ssoState.js:34-44) is signed via the sametoken.signTokenand carrieskind:'sso_tx'with nostageclaim andid:'sso'. It therefore passessessionFromDecodedand is accepted as a validSession(userId:'sso',username:undefined,role:undefined).Impact (bounded)
requireAuth(session.middleware.js:35) re-loads the user withusers.getById('sso')→ no matching row →401. So this does not grant access to protected admin routes. Good.getUserFromRequest(server/src/utils/auth.js:16) returns a truthy{id:'sso'}, andattachSessionattaches a session. The concrete consequence today is a maintenance-mode bypass:siteMode(server/src/middleware/siteMode.js:18) treats any truthygetUserFromRequestas an admin preview and serves gated content. An attacker can obtain ansso_txtoken simply by starting an SSO flow (it is set as an httpOnly cookie), then replay its value as the auth cookie /Authorization: Bearerto bypass the maintenance gate.The real risk is latent: the type-confusion means "any signed token = a session unless it happens to have
stage," so any future code path that trustsattachSession/getUserFromRequestwithout a DB round-trip inherits an auth bypass.Where
server/src/auth/session.service.js:37-48(sessionFromDecoded— only guardsstage)server/src/auth/ssoState.js:34-44(sso_txtoken haskind, nostage)server/src/utils/auth.js:16-21andserver/src/middleware/siteMode.js:18(trust the decoded identity without DB check)Suggested fix (not implemented)
Make session validation positively typed rather than blocklist-typed. Options: stamp real sessions with an explicit
typ:'session'claim and require it insessionFromDecoded; and/or reject any token carrying a non-session marker (decoded.stage || decoded.kind). Ideally give flow/challenge tokens a separate signing key or a mandatoryaud. Also consider makingsiteMode's preview check userequireAuth-strength validation.