GHSA-258c-965c-p3hc: Daptin's Session Management Vulnerability Leads to Insufficient Session Expiration After Password Change
SummaryA session invalidation vulnerability exists in daptin's authentication system where JSON Web Tokens (JWTs) remain fully valid after a user changes their password. The JWT validation middleware (`CheckJWT`) only verifies token signature, expiry, issuer, and signing algorithm — it does not check whether the token was issued before the most recent password change. The password update code path hashes the new password but never calls `InvalidateAuthCacheForEmail()` and never revokes or blacklists existing tokens. This effectively negating password rotation as an incident response control.Vulnerable Files`daptin/server/jwt/jwtmiddleware.go` — JWT validation without session versioning`daptin/server/resource/resource_update.go` — password update without session invalidation`daptin/server/actions/action_generate_jwt_token.go` — JWT claims lack password version`daptin/server/auth/auth.go` — `InvalidateAuthCacheForEmail` exists but not called on update`daptin/server/resource/columns.go` — password change action wiringVulnerable Code Snippet**1. JWT validation checks nothing beyond signature/expiry/issuer (jwtmiddleware.go:232-260):****No check exists for password version, session version, or token revocation status.** A token issued before a password change passes all these checks identically.**2. Password update hashes new password but never invalidates sessions (resource_update.go:282-287):**The new bcrypt hash is stored, but no call to `auth.InvalidateAuthCacheForEmail()` is made, and no token revocation mechanism is triggered.**3. JWT claims lack any password-bound claim (action_generate_jwt_token.go:74-83):**Claims include `email`, `sub`, `name`, `nbf`, `exp`, `iss`, `iat`, `jti` — but **no `pwd_version`** or equivalent claim that could be compared against a server-side value during validation.**4. InvalidateAuthCacheForEmail exists but is NOT called on password update (auth.go:520-530):**This function is called in `resource_create.go:470`, `resource_delete.go:73`, and `dbmethods.go:1194` — but **never** in `resource_update.go`, which is the code path for password changes.PoC (Proof of Concept)Manual Exploitation StepsCreate a user account on the daptin instanceSign in and capture the JWT (this becomes the "stolen" token)Change the user's password via `PATCH /api/user_account/{id}`Reuse the original JWT — it still returns `HTTP 200` with full data accessConfirm the old password no longer works for new login (password did change)Confirm the old token still allows write operations (full CRUD retained)**Result: `HTTP 200`** — write operations also succeed with the old token.Automation PoC**Verified test output:**Recommended Fix**Add a `password_version` column** to the `user_account` table (integer, incremented on each password change)**Include `pwd_version` in JWT claims** at token generation time (`action_generate_jwt_token.go:74`)**Check `pwd_version` during validation** in `CheckJWT()` (`jwtmiddleware.go:232-260`) — compare the claim value against the current database value; reject if mismatched**Call `InvalidateAuthCacheForEmail()`** in `resource_update.go` when a password column is updated, to force the auth cache to re-fetch user state
CVSS v3.1: 6.5