Description
Insufficient session expiry (CWE-613) occurs when an application issues session tokens or authentication credentials that remain valid for an excessively long time or indefinitely. This vulnerability falls under OWASP A07:2021 (Identification and Authentication Failures) and directly extends the window of opportunity for any attack that requires a valid session token: stolen session cookies, XSS-extracted tokens, shoulder-surfed sessions on shared devices, or captured tokens from network interception.
The problem manifests across three distinct patterns: absolute expiry (sessions that never expire or expire after weeks/months), idle expiry (sessions that remain valid despite extended inactivity), and post-logout validity (sessions that continue to work after the user explicitly logs out). All three represent a failure to limit the lifetime of authentication credentials, amplifying the impact of any credential theft.
In mobile applications, the pattern extends to refresh tokens with multi-year lifetimes, JWTs with distant exp claims, and API keys that are never rotated. In web applications, "remember me" functionality implemented with long-lived permanent cookies rather than secure device registration creates persistent session exposure risks.
How It Works
A penetration tester tests session expiry with the following steps:
Absolute expiry test:
# 1. Log in and capture the session cookie
Cookie: session=eyJhbGciOiJIUzI1NiJ9...
# 2. Wait 7 days
# 3. Use the same cookie
curl -H "Cookie: session=eyJhbGciOiJIUzI1NiJ9..." https://target.com/api/profile
# Response: 200 OK with user data — session still valid after 7 days
Post-logout validity test:
# 1. Log in, capture session token
# 2. Log out via the UI
# 3. Replay the session token
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." https://target.com/api/user
# Response: 200 OK — server-side session not invalidated on logout
This is extremely common with JWT-based authentication. JWTs are stateless — there's no server-side session store to invalidate. If a JWT with a 24-hour exp claim is stolen and the user logs out, the token remains cryptographically valid for the remainder of its lifetime. Applications that rely on JWT expiry alone without a token revocation mechanism have no way to invalidate sessions on demand.
Idle timeout bypass:
# Application sets 30-minute idle timeout
# Attacker's script makes a request every 29 minutes to reset the idle timer
while True:
requests.get("https://target.com/api/ping", headers={"Cookie": stolen_session})
time.sleep(1740) # 29 minutes
A stolen session can be kept alive indefinitely by periodic pings if the application uses only idle timeouts without an absolute maximum lifetime.
Impact
- Extended Session Hijacking Window — Stolen session tokens (via XSS, network interception, or device theft) remain exploitable for the session's full lifetime
- Unauthorized Shared-Device Access — Users who access applications from shared computers leave persistent sessions accessible to subsequent users
- Post-Logout Token Replay — Authentication tokens remain valid after the user logs out, enabling replay attacks against any captured token
- Persistence After Password Change — Sessions from before a password change remain active if the application doesn't invalidate all sessions on credential change
Detection
- Test absolute session lifetime — log in, record the session token, wait 24 hours, and reuse it. Document the maximum lifetime before expiry.
- Test post-logout validity — log in, record the token, log out, and immediately replay the token in a direct API request. A 200 response indicates the session was not server-side invalidated.
- Test idle timeout — log in, wait 30 minutes without activity, and attempt to use the session. If the application has no idle timeout, the session persists indefinitely.
- Decode JWT
expclaim — base64-decode the JWT payload and inspect theexptimestamp. Calculate the token lifetime. More than 24 hours for regular sessions or more than 1 hour for sensitive operations is excessive. - Test session persistence after password change — change the account password, then replay a session token captured before the change. The old session should be invalidated.
- Test concurrent session limits — log in from multiple browsers simultaneously. Applications should either limit concurrent sessions or provide a "log out all sessions" mechanism.
Remediation
Implement both absolute and idle expiry. Set a hard absolute maximum (e.g., 12 hours for web apps, 24 hours for mobile) and an idle timeout (e.g., 30 minutes for sensitive apps, 2 hours for regular apps). The idle timer should reset on activity, but the absolute timer must not:
// Express.js — session with both timeouts
app.use(session({
secret: process.env.SESSION_SECRET,
cookie: {
maxAge: 12 * 60 * 60 * 1000, // 12 hour absolute maximum
secure: true,
httpOnly: true,
sameSite: 'strict'
},
rolling: true, // Reset idle timer on activity
resave: false,
saveUninitialized: false
}));
Implement server-side session invalidation. For JWT-based systems, maintain a token denylist (Redis works well) or use short-lived access tokens (15 minutes) with separate refresh token rotation:
// On logout — add token JTI to denylist
await redis.setex(`denylist:${decoded.jti}`, decoded.exp - Date.now()/1000, '1');
Invalidate all sessions on password change. When a user changes their password, revoke all other active sessions to limit the impact of credential-based takeover.
Provide session management UI. Allow users to view and revoke active sessions, including location and device information. This is both a security control and a trust-building feature.
