DEV Community

27Rafa
27Rafa

Posted on

I built a tool to test whether logout actually invalidates sessions

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:

  1. Finds the logout request
  2. Executes it
  3. Replays every authenticated GET/HEAD endpoint using the old session cookie
  4. 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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Log in as the user
  2. Save the session cookie
  3. Capture every request the app makes
  4. Click logout
  5. Replay every captured request with the saved cookie
  6. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Drop it into a security pipeline:

./ghostsession.py session.har || echo "CWE-613 found"
Enter fullscreen mode Exit fullscreen mode

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)