Back to the index

LAB FAIL

Insufficient Session Expiration - Authentication - JWT Lifetime

Insufficient Session Expiration — Authentication — JWT Lifetime

CWE-613: Insufficient Session Expiration · OWASP A07:2021 - Identification and Authentication Failures

Vulnerability

[FAIL] Insufficient Session Expiration - Authentication - JWT Lifetime

Description

Issued JWT tokens contain no exp claim, so they never expire, and there is no token revocation mechanism. The token is also accepted from several locations at once, including URL query parameters, and in the web interface it is stored in localStorage where JavaScript can read it.

Impact

Medium: a token that leaks once remains valid forever. There is no rotation or revocation, so a single leak grants permanent access to the victim's account. Accepting tokens via query parameters increases the chance of leakage because URLs are recorded in browser history, server logs, and the Referer header. Storing the token in localStorage makes it stealable via XSS, which was proven in the Stored Cross-Site Scripting finding where an administrator JWT was successfully captured.

CWE

CWE-613: Insufficient Session Expiration

OWASP

A07:2021 - Identification and Authentication Failures

Scenario

Token analysis: an issued token is decoded and contains only the user_id, username, is_admin, and iat claims; there is no exp claim at all. Verification: the same token is still accepted by a protected endpoint. Supporting evidence: the token is also accepted when sent as a URL query parameter, not only in the Authorization header.

Endpoint

POST http://127.0.0.1:5001/login (token issuance) GET http://127.0.0.1:5001/dashboard (token acceptance)

Affected feature

JWT-based session management

Remediation

- Add a short-lived exp claim and implement revocable refresh tokens. - Maintain a revocation list (denylist) or use token identifiers so sessions can be terminated during an incident. - Accept tokens only from the Authorization header; stop reading them from query parameters and forms. - Move session tokens into HttpOnly, Secure, and SameSite cookies so JavaScript cannot reach them.

Proof of concept

  1. (Screenshot 1) ANALYSIS: the login token is decoded -> it contains only user_id, username, is_admin, and iat; no exp claim exists.
  2. (Screenshot 2) VERIFICATION: the same token is accepted by a protected endpoint -> HTTP 200.
  3. (Screenshot 3) IMPACT: the same token is also accepted when sent as a URL query parameter -> HTTP 200, increasing the chance of leakage via logs and Referer.

The complete record

LAB All targets are lab systems. Nothing here is a client engagement. Evidence is shown as recorded; secrets are blacked out.

Inspect JWT lifetime

  1. Issued token [REDACTED-LAB-TOKEN]
  2. Decoded payload { "user_id": 2, "username": "pentest_attacker", "is_admin": false, "iat": 1790737980 }
  3. Claims present iat, is_admin, user_id, username
  4. Expiry claim (exp) absent
Screenshot
HTTP request
GET /dashboard HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Authorization: Bearer [REDACTED-LAB-TOKEN]

HTTP response
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 27266
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:13:01 GMT

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dashboard - VulnBank</title>
    <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
    <link rel="icon" type="image/svg+xml" href="/static/favicon-16.svg" sizes="16x16">
    <link rel="stylesheet" href="/static/style.css">
    <link rel="stylesheet" href="/static/dashboard.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script>document.documentElement.setAttribute("data-theme", localStorage.getItem("vb-theme") || "light");</script>
    <script src="/static/vuln-disclaimer.js" defer></script>
</head>
<body>
    <a href="#main-content" class="skip-link">Skip to main content</a>

    <!-- Mobile menu toggle -->
    <button class="menu-toggle" onclick="toggleSidePanel()" aria-label="Toggle navigation menu">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
    </button>
    <div class="sidebar-overlay" id="sidebarOverlay" onclick="toggleSidePanel()"></div>

    <!-- Sidebar -->
    <aside class="sidebar" id="sidebar" role="navigation" aria-label="Main navigation">
        <div class="sidebar-logo">
            <div class="sidebar-logo-icon">
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"></line><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path></svg>
            </div>
            <span class="sidebar-logo-text">VulnBank</span>
        </div>
        <nav class="sidebar-nav">
            <div class="nav-section-label">Main</div>
            <a href="#profile" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
                Profile
            </a>
            <a href="#transfers" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
                Money Transfer
            </a>
            <a href="#loans" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg>
                Loans
            </a>
            <div class="nav-section-label">Activity</div>
            <a href="#transactions" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>
                Transactions
            </a>
            <a href="#virtual-cards" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect><line x1="1" y1="10" x2="23" y2="10"></line></svg>
                Virtual Cards
            </a>
            <a href="#bill-payments" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line></svg>
                Bill Payments
            </a>
            
        </nav>
        <div class="sidebar-footer">
            <img id="sidebar-avatar" class="sidebar-avatar" src="/static/uploads/966073_payload.html" alt="">
            <div class="sidebar-user-info">
                <div class="sidebar-user-name">pentest_attacker</div>
                <div class="sidebar-user-role">Personal Account</div>
            </div>
            <button class="sidebar-logout" onclick="logout()" aria-label="Sign out">
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
            </button>
        </div>
    </aside>

    <!-- Topbar -->
    <header class="topbar">
        <div class="topbar-left">
            <span class="topbar-greeting">Welcome back, pentest_attacker</span>
            <span class="topbar-date" id="current-date"></span>
        </div>
        <div class="topbar-right">
            <button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle dark mode">
                <span class="toggle-icon toggle-icon-sun">☀️</span>
                <span class="toggle-slider"></span>
                <span class="toggle-icon toggle-icon-moon">🌙</span>
            </button>
            <button class="topbar-icon-btn" aria-label="Notifications">
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 

[... 21259 more bytes not shown ...]
Screenshot
HTTP request
GET /dashboard?token=[REDACTED-LAB-TOKEN] HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive

HTTP response
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 27266
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:13:03 GMT

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dashboard - VulnBank</title>
    <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
    <link rel="icon" type="image/svg+xml" href="/static/favicon-16.svg" sizes="16x16">
    <link rel="stylesheet" href="/static/style.css">
    <link rel="stylesheet" href="/static/dashboard.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script>document.documentElement.setAttribute("data-theme", localStorage.getItem("vb-theme") || "light");</script>
    <script src="/static/vuln-disclaimer.js" defer></script>
</head>
<body>
    <a href="#main-content" class="skip-link">Skip to main content</a>

    <!-- Mobile menu toggle -->
    <button class="menu-toggle" onclick="toggleSidePanel()" aria-label="Toggle navigation menu">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
    </button>
    <div class="sidebar-overlay" id="sidebarOverlay" onclick="toggleSidePanel()"></div>

    <!-- Sidebar -->
    <aside class="sidebar" id="sidebar" role="navigation" aria-label="Main navigation">
        <div class="sidebar-logo">
            <div class="sidebar-logo-icon">
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"></line><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path></svg>
            </div>
            <span class="sidebar-logo-text">VulnBank</span>
        </div>
        <nav class="sidebar-nav">
            <div class="nav-section-label">Main</div>
            <a href="#profile" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
                Profile
            </a>
            <a href="#transfers" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
                Money Transfer
            </a>
            <a href="#loans" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg>
                Loans
            </a>
            <div class="nav-section-label">Activity</div>
            <a href="#transactions" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>
                Transactions
            </a>
            <a href="#virtual-cards" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect><line x1="1" y1="10" x2="23" y2="10"></line></svg>
                Virtual Cards
            </a>
            <a href="#bill-payments" class="nav-link" onclick="setActiveLink(this)">
                <svg class="nav-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line></svg>
                Bill Payments
            </a>
            
        </nav>
        <div class="sidebar-footer">
            <img id="sidebar-avatar" class="sidebar-avatar" src="/static/uploads/966073_payload.html" alt="">
            <div class="sidebar-user-info">
                <div class="sidebar-user-name">pentest_attacker</div>
                <div class="sidebar-user-role">Personal Account</div>
            </div>
            <button class="sidebar-logout" onclick="logout()" aria-label="Sign out">
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
            </button>
        </div>
    </aside>

    <!-- Topbar -->
    <header class="topbar">
        <div class="topbar-left">
            <span class="topbar-greeting">Welcome back, pentest_attacker</span>
            <span class="topbar-date" id="current-date"></span>
        </div>
        <div class="topbar-right">
            <button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle dark mode">
                <span class="toggle-icon toggle-icon-sun">☀️</span>
                <span class="toggle-slider"></span>
                <span class="toggle-icon toggle-icon-moon">🌙</span>
            </button>
            <button class="topbar-icon-btn" aria-label="Notifications">
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 

[... 21259 more bytes not shown ...]
Screenshot

← Previous case: Excessive Data Exposure - GraphQL - Schema Introspection Next case: JWT Algorithm Confusion - Authentication - alg none →