Skip to main content
VulnerabilityPublished

github.com/daptin/daptin · Daptin's Session Management Vulnerability Leads to Insufficient Session Expiration After Password Change

Affects github.com/daptin/daptinSource GCVE Database

0

CVSS 6.5 Medium : upgrade to 0.11.8 when convenient.

Data as of 27 Oct 2026, 00:07 UTC

CVSS 3.1 base score
6.5Medium
How severe the flaw is on the 0–10 CVSS scale.
Patch
Available – fixed in 0.11.8
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N

What to do

Upgrade github.com/daptin/daptin to 0.11.8.

Affected software

ProductAffectedFixed in
Gogithub.com/daptin/daptinAffected
  • <0.11.8
Fixed in
  • 0.11.8

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

### Summary A 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 wiring #### Vulnerable Code Snippet **1. JWT validation checks nothing beyond signature/expiry/issuer (jwtmiddleware.go:232-260):** ```go // Now parse the token parsedToken, err := jwt.Parse(token, m.Options.ValidationKeyGetter) // Check if there was an error in parsing... if err != nil { m.logf("Error parsing token: %v", err) m.Options.ErrorHandler(w, r, err.Error()) return nil, fmt.Errorf("Error parsing token: %v", err) } if parsedToken.Claims.(jwt.MapClaims)["iss"] != m.Options.Issuer { return nil, fmt.Errorf("Invalid issuer: %v", parsedToken.Header["iss"]) } if m.Options.SigningMethod != nil && m.Options.SigningMethod.Alg() != parsedToken.Header["alg"] { // ... algorithm check } // Check if the parsed token is valid... if !parsedToken.Valid { m.logf("Token is invalid") m.Options.ErrorHandler(w, r, "The token isn't valid") return nil, errors.New("Token is invalid") } ``` **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):** ```go if col.ColumnType == "password" { val, err = BcryptHashString(val.(string)) if err != nil { log.Errorf("Failed to convert string to bcrypt hash, not storing the value: %v", err) continue } } ``` 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):** ```go token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "email": existingUser["email"], "sub": daptinid.InterfaceToDIR(existingUser["reference_id"]).String(), "name": existingUser["name"], "nbf": timeNow.Unix(), "exp": timeNow.Add(time.Duration(d.tokenLifeTime) * time.Hour).Unix(), "iss": d.jwtTokenIssuer, "iat": timeNow.Unix(), "jti": u.String(), }) ``` 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):** ```go func InvalidateAuthCacheForEmail(email string) { if olricCache == nil { return } _, err := olricCache.Delete(context.Background(), email) if err != nil { log.Warnf("failed to invalidate auth cache for %s: %v", email, err) } } ``` 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 Steps 1. Create a user account on the daptin instance 2. Sign in and capture the JWT (this becomes the "stolen" token) 3. Change the user's password via `PATCH /api/user_account/{id}` 4. Reuse the original JWT — it still returns `HTTP 200` with full data access 5. Confirm the old password no longer works for new login (password did change) 6. Confirm the old token still allows write operations (full CRUD retained) **Result: `HTTP 200`** — write operations also succeed with the old token. #### Automation PoC ``` # VULN-04: No Session Invalidation After Password Change # CWE-613 | daptin v0.9.82 # Proves: old JWT stays valid after password change $BASE = "http://127.0.0.1:6336" Write-Host "`n===== VULN-04: Session Invalidation Test =====`n" -ForegroundColor Cyan # Step 0: Clean restart Write-Host "[0] Restarting container..." -ForegroundColor Yellow docker compose -f "c:\Users\Vashu\Desktop\Projects\ZeroDay\cve_hunt\daptin\docker-compose.yml" restart daptin Start-Sleep 8 # Step 1: Create victim user Write-Host "[1] Creating victim user..." -Foregr

References

Community reviews

Loading reviews…