Lesson pack 13 · Core · 120 minutes
Sessions, tokens and access control
Read and forge JWTs and Flask sessions, break the assumptions behind a session cookie, and win the races that a request boundary creates.
Print this page for a paper plan - the navigation and links drop out.Student-facing version
Before the session
- Nothing to install. Students need a browser and https://ctfpal.com. Confirm the room can reach it once; after that it works offline.
- Read the lesson yourself first - about 26 minutes.
- Have one worked example ready to paste. The classroom link builder on the instructor page turns it into a URL that opens preloaded.
Objectives
Written as things a student can do afterwards, so they can be assessed rather than asserted.
- Decode a JWT and identify the attack its header enables
- Forge an alg=none token and recover a weak HMAC secret offline
- Unpack and re-sign a Flask session once the secret key is known, and find that key first
- Choose between horizontal and vertical access-control tests, and prove each with two accounts
- Exploit a check-then-act window with concurrent requests, and explain why a retry loop is not the same thing
- Recognise a request-smuggling primitive from a header pair a proxy and a server read differently
Running order (120 min)
| Time | What happens |
|---|---|
| 0:00-0:12 | Frame the problemWhat the category looks like when you meet it cold, and why the naive approach fails. |
| 0:12-0:36 | Teach the methodThe technique itself, on the board or from the lesson. No tools open yet. |
| 0:36-1:06 | Demonstrate liveSame technique, in the workspace, on your worked example. Narrate every choice. |
| 1:06-1:48 | Practice setStudents work the challenges. Circulate rather than present. |
| 1:48-2:00 | Checkpoint and wrapCollect the artefact, name what comes next. |
Tools used
- JWT decoder and signature verifier - Decode a JSON Web Token’s header and payload and verify HS256/HS384/HS512 signatures against a known secret - all locally.https://ctfpal.com/?tool=jwt-decoder
- JWT alg=none bypass generator - Forge an unsigned JWT by setting the algorithm to none, and understand why some libraries still accept it.https://ctfpal.com/?tool=jwt-none-algorithm
- JWT secret brute force - Recover a weak HMAC signing secret from a JWT by testing a wordlist against the token’s own signature, in the browser.https://ctfpal.com/?tool=jwt-secret-bruteforce
- Flask session cookie decoder - Decode and verify Flask’s itsdangerous session cookies, with automatic zlib detection and both key-derivation schemes.https://ctfpal.com/?tool=flask-session-decoder
- HTTP request replayer - Craft and replay HTTP requests with arbitrary methods, headers, and bodies, and read the raw response - a Repeater that runs in your browser.https://ctfpal.com/?tool=http-request-replayer
- HTTP security header analyzer - Analyse CSP, HSTS, X-Frame-Options, and CORS headers on a response - and find the gaps in a Content-Security-Policy that make XSS exploitable.https://ctfpal.com/?tool=security-header-analyzer
Reading
- Attacking JWTs: alg=none, algorithm confusion, and the header fields nobody audits - 7 min. A JSON Web Token is a signed claim you were handed and asked to give back. Every classic JWT bug is a place where the verifier lets the token choose how it is verified - alg=none, RS256 to HS256 confusion, kid injection, and attacker-hosted key URLs.
- Sessions, cookies, CORS and CSRF: the browser's trust rules - 7 min. Which origin can read what, why a cookie is scoped differently from everything else in the browser, and the four ways a session is stolen without ever finding an XSS: CSRF, a permissive CORS policy, cookie tossing, and session fixation.
- Race conditions: spending the same balance twice - 6 min. Limit overruns, TOCTOU on the filesystem, and the single-packet attack that removes network jitter from the equation. How to recognise a race in a feature description and how to actually win one.
- Request smuggling: when the proxy and the server disagree - 6 min. CL.TE, TE.CL, TE.TE and the HTTP/2 downgrade desyncs. How two servers reading the same bytes can disagree about where one request ends, what that buys you, and how to detect it without wrecking the target.
Practice set
Real picoCTF challenges tagged with this module’s techniques, easiest first. Assign the first three in class and the rest as homework.
- Cookie Monster Secret Recipe - picoCTF 2025, easy
- Cookies - picoCTF 2021, easy
- Crack the Gate 1 - picoMini by CMU-Africa, easy
- logon - picoCTF 2019, easy
- Old Sessions - picoCTF 2026, easy
- Apriti sesamo - picoCTF 2025, medium
- Credential Stuffing - picoCTF 2026, medium
- Fool the Lockout - picoCTF 2026, medium
- Irish-Name-Repo 3 - picoCTF 2019, medium
- JAuth - picoGym Exclusive, medium
- Live Art - picoCTF 2022, hard
- tic-tac - picoCTF 2023, hard
Checkpoint (gradeable)
Take a JWT-protected endpoint and reach an admin-only response, stating which of the three token weaknesses you used and why the other two did not apply.
Deliberately a produced artefact rather than a quiz question: it is either there or it is not, which makes it fast to mark and hard to bluff. Every tool in ctfpal is deterministic, so two students who did the work correctly hand in the same value.
Where the room gets stuck
Students conflate 'I can read the token' with 'I can change the token'. Decode a JWT in front of them, edit the role claim, watch the server reject it, and only then introduce the three ways the signature actually fails. The rejection is the lesson.
- Editing the payload of a signed token and expecting it to be accepted. The attack has to defeat the signature - alg confusion, a known secret, or a kid that points somewhere useful - not ignore it.
- Decoding a Flask session and calling it forged. It is signed too; without the secret key you have read access and nothing more.
- Testing a race with a for loop. Sequential requests never overlap; the window needs concurrency, and often needs the requests to arrive in the same packet.
- Cracking an HMAC secret against re-encoded JSON. The signature covers the base64url header and payload exactly as transmitted, so pretty-printing or reordering the claims before testing candidates makes the correct secret look wrong.
- Fixing an expired token by editing exp and nothing else. Expiry is checked after the signature, so a token that fails verification never reaches the claim you changed and the error tells you nothing about it.
If a student wants the subject, not the answer
Chapter-level references, so a student can be pointed at twenty pages rather than at a book. Nothing here is required to complete the module.
- Hacking APIs, Corey Ball. Chapter 8, Attacking Authentication. Token handling as an attack surface in its own right, including the JWT cases in deployment terms.
- Hacking APIs, Corey Ball. Chapter 10, Exploiting Authorization. The two-account method for proving an access-control bug, which is what turns a suspicion into a finding.
- Real-World Bug Hunting, Peter Yaworski. Chapter 15, Race Conditions. Real reports where the whole bug is a window of a few milliseconds.
- Bug Bounty Bootcamp, Vickie Li. Chapter 20, Single-Sign-On Security Issues. Where session material crosses a trust boundary between two systems, which is where most token bugs actually live.
If you finish early
- Hand out a challenge from the cross-CTF index in this category - each one has published solutions to compare afterwards.
- Run the same input through Identify and let the class argue with the ranking. Disagreeing with a confidence score is where the technique actually lands.
- Ask a student to break their own example - construct an input that defeats the tool, and explain why.
Take this into a room
Markdown files, built here in your browser. They print, they open in anything, and they carry the challenge text - so the session works with no network in the room.