JAuth

Published: March 5, 2024

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:52680/ 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

  1. Step 1Decode the token
    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
    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.
    printf '{"typ":"JWT","alg":"none"}' | base64 | tr -d '=' | tr '+/' '-_'
    printf '{"role":"admin", ...}' | base64 | tr -d '=' | tr '+/' '-_'
    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
    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.

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

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

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

Want more picoGym Exclusive writeups?

Useful tools for Web Exploitation

Related reading

What to try next