SECURE_NODE // ATHENS_GR
← BACK TO RESEARCH Technique · Web Application · Session Management

JWT Session Token Exposed in URL Query Parameter

24 JUN 2026| ≈ 4 MIN READ| JWTSessionHijackingAccountTakeoverBrokenAuth
SANITIZED ▸ Methodology only. All hostnames, usernames, and token values are lab placeholders. No client name, real domain, real credential, or live token appears here. Assessment was authorized and scoped in writing.

After a successful SSO login, the application issued a signed JWT session token and delivered it to the browser as a plaintext URL query parameter in a GET redirect. The token landed in /app/members/?token=<jwt> and was accepted without session binding, IP binding, or single-use enforcement. Pasting the captured URL into a clean incognito browser with no existing cookies produced a fully authenticated session — including administrative access — confirming complete account takeover with nothing more than the URL.

Why a token in a URL is a different threat model

Secrets in request bodies or Authorization headers are transient — they travel once, between two parties, and disappear. A token in a URL query parameter is a different problem: the URL is recorded everywhere. Server access logs write it. The browser stores it in history. Every proxy and reverse proxy in the chain logs the full request line. If the page subsequently loads any external resource, the token appears in the Referer header sent to that third-party origin — analytics scripts, CDN assets, or widgets. A token with a long validity window, placed in a URL, is a credential that has been written to half a dozen artefacts the user has no visibility into.

SEVERITY
High
full account takeover
COMPLEXITY
Low
URL paste, no other steps
KEY FLAW
Token in GET param
no binding, no single-use

The proof of concept

# SSO callback delivers session token as a GET query parameter
GET /app/members/?token=<signed-jwt> HTTP/1.1
Host: app.acme-lab.test
# no session cookie required — token alone establishes the session

# same token replayed from isolated incognito browser — no prior cookies
GET /app/members/?token=<same-signed-jwt> HTTP/1.1
Host: app.acme-lab.test
Cookie: (empty)

HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=<new-session>; Path=/; HttpOnly
# authenticated session issued — full admin access confirmed

# second replay — accepted again, no invalidation on first use
# jti field: static string, not a unique UUID per issuance
# token valid until expiry regardless of how many times it is redeemed

The JWT payload carried a static jti field with a fixed string value rather than a unique UUID per issuance. Without a unique jti the server has no mechanism to track whether a token has already been redeemed — replay detection is structurally absent.

Where the token leaks without attacker involvement

Because the token sits in the URL, it is recorded in at least four artefact classes passively: server access logs at the application tier, browser history on the client machine, proxy and WAF logs in the network path, and Referer headers sent to any external origin the landing page loads. Any party with read access to any of these artefacts can replay the token within its validity window and authenticate as the target user — no active interception required.

DAY 1 — SSO FLOW MAPPING

SSO authentication flow captured in Burp. Callback redirect observed delivering token as a ?token= query parameter on the GET request to the application landing page.

DAY 1 — REPLAY CONFIRMED

Token URL pasted into isolated incognito browser. No cookies, no prior session. Full authenticated session issued — admin-level access confirmed from the server response.

DAY 1 — NO SINGLE-USE ENFORCEMENT

Same token replayed a second time — accepted again. Static jti field confirmed. No invalidation on first redemption. Token remains valid until its expiry timestamp.

DAY 1 — LEAKAGE PATHS DOCUMENTED

Token confirmed in server access logs and Referer header. External resource loaded by the landing page received the full token URL in the outbound Referer on page load.

DAY 2 — REPORT

Full finding documented. Replay evidence, leakage paths, and static jti value all captured with request/response evidence. Raised as High severity.

Fixing the token delivery

  • Transmit the JWT via POST body or Authorization header rather than a URL query parameter. The SSO callback should POST the token to the application rather than redirecting with it in the URL — this removes it from logs, browser history, and Referer headers in a single architectural change.
  • Implement single-use enforcement at the callback endpoint. Invalidate the token server-side immediately upon first successful redemption and reject any subsequent presentation with HTTP 401. A short-lived, single-use token eliminates the replay window even if the URL is captured.
  • Replace the static jti field with a unique UUID generated per issuance. Store each redeemed jti value server-side and reject any token whose jti has already been seen — this is the mechanism that makes single-use enforcement structurally possible.
  • Bind the token at issuance to the originating session context or source IP and validate this binding upon redemption. A token captured from one context should not be redeemable from a different device or network location within the same validity window.

The takeaway

A JWT in a URL is a credential in a log file. The session binding, replay enforcement, and leakage surface problems are independent — each is fixable alone — but they all stem from one root cause: the delivery mechanism. Move the token out of the URL and the rest of the attack surface collapses with it.