Skip to main content

JAuth picoGym Exclusive Solution

Exploit a web authentication vulnerability involving improperly validated tokens to gain admin access.

Published: March 5, 2024Updated: August 25, 2026

Description

JAuth stores session state in a JWT cookie. Because the token accepts alg=none, you can rewrite the payload to grant yourself admin access and bypass authentication.

JWT tampering

Visit http://saturn.picoctf.net:<PORT_FROM_INSTANCE>/ and log in with the provided test/Test123! credentials.

Open DevTools → Application → Cookies to copy the issued JWT. It contains three base64url segments separated by dots.

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Decode the token
    Observation
    The session cookie is a long opaque string in three dot-separated segments, which is a JWT. Decode the header and payload and see which claims control authorization.
    Base64-decode the first two segments. The header uses HS256 and the payload contains role:"user". Leave the signature segment blank for now.
    Learn more

    A JSON Web Token (JWT) is a compact, URL-safe token format used for authentication and authorization. It consists of three base64url-encoded segments separated by dots: header.payload.signature. The header specifies the token type and signing algorithm. The payload contains claims - key-value pairs like sub (subject), exp (expiration), and application-specific fields like role. The signature proves the header and payload have not been tampered with.

    Base64url is a variant of standard Base64 that replaces + with - and / with _, and omits padding (=). This makes the encoded string safe to include in URLs and cookies without percent-encoding. Decoding the token's first two segments gives you the raw JSON objects, which you can then read and modify.

    Tools like jwt.io (a web-based decoder) and the jwt command-line tool make decoding JWTs trivial. In a CTF, quickly decoding a JWT to inspect its payload is a standard first step whenever you see a suspiciously opaque cookie or authorization header - the structure is always the same three dot-separated segments.

  2. Step 2Forge an unsigned token
    Observation
    The header names HS256 and the payload carries a role claim of user. If the server honors an algorithm of none, the role rewrites to admin with no valid signature at all.
    Change the header to {"typ":"JWT","alg":"none"} and the payload to set "role":"admin". Base64url-encode both segments without padding and concatenate them with a trailing dot to indicate an empty signature.
    bash
    printf '{"typ":"JWT","alg":"none"}' | base64 | tr -d '=' | tr '+/' '-_'
    bash
    printf '{"role":"admin", ...}' | base64 | tr -d '=' | tr '+/' '-_'

    Expected output

    eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0
    What didn't work first

    Tried: Omitting the trailing dot and submitting header.payload as the forged token

    The spec requires three dot-separated segments, so a two-segment token is rejected as malformed before the algorithm is ever read. The trailing dot is not decoration; it is the empty signature field, and the token needs it to parse.

    Tried: Using standard Base64 padding (with = characters) instead of stripping it for base64url

    Cookies and URLs treat the equals sign as special, so padded base64 in a cookie value gets corrupted or rejected. Base64url strips padding entirely, and the server's decoder infers the length from the segment.

    Learn more

    The alg:none vulnerability is a well-known JWT flaw. The JWT specification originally allowed "alg": "none" to indicate an unsigned token (useful in trusted, internal contexts). Vulnerable servers that accept this value skip signature verification entirely - meaning any client can forge a valid-looking token simply by setting the algorithm to none and providing no signature. The trailing dot in header.payload. represents the empty signature field.

    To forge the token: encode the modified header JSON with base64url (strip padding, swap + and /), do the same for the modified payload, then concatenate them as encodedHeader.encodedPayload. (note the trailing dot). This produces a syntactically valid JWT that many vulnerable libraries will accept as genuine because they check the algorithm field instead of enforcing that signatures are always required.

    This vulnerability was disclosed in 2015 and has affected real-world implementations in multiple languages. Properly hardened JWT libraries either reject alg:none entirely or require callers to explicitly allowlist acceptable algorithms. When reviewing any system that uses JWTs, checking that the server enforces algorithm restrictions is a standard item on the security review checklist. Related attacks include the algorithm confusion attack (switching RS256 to HS256 and signing with the public key) and header injection (pointing the jku or x5u header at an attacker-controlled key set).

  3. Step 3Swap the cookie
    Observation
    The admin page is gated on that cookie alone. Replace it with the forged token in DevTools, reload, and the server grants admin with no further credentials.
    Replace the existing JWT cookie with the forged one (header.payload.) and refresh the page. The admin view appears immediately and prints the flag.
    Learn more

    Browser DevTools (F12) provides direct access to cookies via the Application tab. You can double-click any cookie value to edit it in place, then reload the page to send the new value to the server. This is the fastest way to test JWT forgeries without writing any exploit code - the browser handles the HTTP request with the modified cookie automatically.

    The fact that changing a single field in the JWT payload (role: "user" to role: "admin") immediately grants admin access illustrates a fundamental web security principle: never trust client-supplied data for authorization decisions. The server must verify both the token's signature and that the claims it contains are legitimate. Storing authorization state in an unverified client-side token is equivalent to having no authorization at all.

    In a real penetration test, JWT vulnerabilities are tested using tools like jwt_tool (a dedicated JWT attack suite), Burp Suite (intercept and modify tokens in the proxy), and custom scripts. Finding that a production application accepts alg:none tokens would be a critical severity finding - it completely bypasses authentication and allows full privilege escalation without knowing any credentials or secrets.

Interactive tools
  • JWT DecoderDecode JSON Web Tokens and inspect the header, payload, and signature. Useful for web exploitation challenges.
Alternate Solution

Paste your JWT into the JWT Decoder tool on this site to instantly see the decoded header and payload. This makes it easy to confirm the algorithm and identify which claim to modify before forging the token.

Flag

Reveal flag

picoCTF{succ3ss_@u7h3nt1c@710...4eacf}

JWTs that declare alg:"none" trust client-side data blindly, so editing the payload is enough to escalate privileges.

Key takeaway

A JWT is not encrypted, and anyone holding one reads its payload. The entire guarantee is the signature, which proves the server issued that exact header and payload unmodified. Accept an algorithm of none and verification is skipped, so any client forges any claim, privilege fields included. Algorithm confusion and key-reference injection share the root cause: trusting the token to name its own algorithm or key rather than fixing both server-side.

Related reading

Useful tools for Web Exploitation

Where to go next