Test case 08 / 26FAIL
Insufficient Session Expiration - Authentication - JWT Lifetime
Lab environment — not a production system
Screenshots and raw traffic are not translated; text within them may be in another language.
vuln-bank/Evidence/[FAIL] Insufficient Session Expiration - Authentication - JWT Lifetime
01 — Notes
Test notes
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.
View source note (English)
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.
POC:
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.
02 — Screenshots
Screenshots (2)
03 — Process details
Process details
Credential values are masked in this HTML view; references to complete tokens describe the source capture.
PROCESS 01 / 1
Inspect JWT lifetime
Issued token
[REDACTED-LAB-JWT]Decoded payload
{
"user_id": 2,
"username": "pentest_attacker",
"is_admin": false,
"iat": 1790737980
}Claims present
iat, is_admin, user_id, usernameExpiry claim (exp)
absentCredential values are masked in this HTML view; references to complete tokens describe the source capture.
04 — Requests and responses
HTTP captures
Some credential values are masked in this public HTML view. This does not mean every sensitive value or linked file has been redacted.
CAPTURE 01 / 2
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/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 ...]CAPTURE 02 / 3
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/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 ...]