When you click "logout," you expect your session to be dead. Sometimes it isn't.
The server clears the cookie in your browser. The UI redirects you to the login page. Everything looks right. But the session token is still valid server-side — and anyone who captured it before you logged out can keep using it.
This is CWE-613: Insufficient Session Expiration. It's the bug that shows up in every security review and almost never gets tested, because verifying it by hand takes 20 minutes per app in Burp Suite.
I got tired of doing it by hand. So I built ghostsession.
What it does
Takes one HAR file containing a full session — login, activity, logout — and:
- Finds the logout request
- Executes it
- Replays every authenticated GET/HEAD endpoint using the old session cookie
- Reports which endpoints still return authenticated data
If any do, the session was never invalidated. Anyone holding that old cookie still has the account.
The demo
I built a deliberately vulnerable local server. The /logout endpoint sends a Set-Cookie header that tells the browser to forget the session — but the server keeps the token alive in memory.
$ ./ghostsession.py session.har
[*] Parsed 8 entries from session.har
[*] Logout found at entry #6: POST http://127.0.0.1:8001/logout
[*] Old session cookie: session=2437dcdfe9bca801c35746c463db90f3...
[*] 5 authenticated endpoint(s) to replay after logout
[*] Executing logout: POST http://127.0.0.1:8001/logout
[+] Logout responded: 200
[*] Replaying 5 endpoint(s) with OLD cookie...
----------------------------------------------------------------------
Target : http://127.0.0.1:8001
Logout request : http://127.0.0.1:8001/logout
Session cookie : session=2437dcdfe9bca801c35746c463db90f3...
Endpoints tested: 5
Vulnerable : 5
----------------------------------------------------------------------
[!] VULNERABLE — session survives logout
[CRITICAL] GET http://127.0.0.1:8001/me
status: 200 -> 200 similarity: 1.0
[CRITICAL] GET http://127.0.0.1:8001/api/settings
status: 200 -> 200 similarity: 1.0
CWE-613: Insufficient Session Expiration
OWASP : WSTG-SESS-06 / WSTG-SESS-07
Five endpoints still returned full account data after logout. The bug is unambiguous: the "logout" button is cosmetic.
Why this is under-tested
Testing it manually means:
- Log in as the user
- Save the session cookie
- Capture every request the app makes
- Click logout
- Replay every captured request with the saved cookie
- Eyeball each response to see which ones still work
Doing that across 20 endpoints in Burp takes an hour. Multiply by every app you review. Most people just check whether the cookie got cleared and move on.
ghostsession automates the whole loop.
The one design choice that matters
Everything is pure Python standard library. No pip install. No Go binary. No Burp extension. No Docker. It's one file.
curl -O https://raw.githubusercontent.com/277levi/ghostsession/main/ghostsession.py
chmod +x ghostsession.py
./ghostsession.py session.har
That's the whole install. It runs on any box with Python 3.8+, including locked-down pentest jump boxes where you can't install anything.
The detection logic
Three things had to be right for this to produce signal instead of noise:
Finding the logout request. Ghostsession matches URLs against /logout, /signout, /api/auth/logout, /sessions/destroy, and similar patterns. It prefers POST when there are multiple matches.
Extracting the old cookie. It walks backward through the HAR from the logout entry, picks the session cookie off the last authenticated request.
Detecting a real leak. Same HTTP status before and after is not enough. A logout that correctly returns a 200 "please log in" page would look identical at the status level. So the tool also requires the response body to be at least 70% similar, using Jaccard similarity on 4-gram shingles. That catches the case where the server quietly served a login page instead of the account data.
What it doesn't do
Honest limitations:
- Only tests read-only methods (GET/HEAD). Post-logout write testing is planned for v0.2.
- Assumes the HAR contains a logout request. If you didn't capture one, the tool errors out.
- Doesn't test session fixation — only invalidation.
- If the app rotates the session cookie on logout and invalidates the old one, ghostsession correctly reports "OK."
Exit codes for CI
0 = session properly invalidated
1 = session survived logout (vulnerable)
2 = fatal error
Drop it into a security pipeline:
./ghostsession.py session.har || echo "CWE-613 found"
The repo
https://github.com/277levi/ghostsession
MIT licensed. Pure stdlib. Issues and PRs welcome.
I built this because I got tired of checking the same bug by hand. If you do security reviews, I hope it saves you the same hour.
If you have a HAR file from a recent session and want to see whether the app got it right — run it and let me know what you find. Genuinely curious how common this is in the wild.
Top comments (0)