JaWT Scratchpad picoCTF 2019 Solution

Published: April 2, 2026

Description

Use the JaWT Scratchpad application. The admin user has the flag.

  1. Step 1Log in and capture the JWT
    Register or log in as any user (e.g., 'guest'). After login, inspect your cookies in browser DevTools. You will find a JWT (JSON Web Token) cookie - it looks like three base64url segments separated by dots.
    Learn more

    A JWT has three parts: header.payload.signature. Each part is base64url-encoded. The header specifies the algorithm (e.g., HS256), the payload contains claims like the username, and the signature verifies integrity.

    The payload is not encrypted - only signed. Anyone can decode and read the claims, but modifying them invalidates the signature (unless you can forge it).

  2. Step 2Decode the JWT
    Paste your JWT into jwt.io or use Python to base64-decode the payload. You will see something like {"user": "guest"}. Your goal is to change this to {"user": "admin"}.
    python
    python3 -c "
    import base64, json
    token = 'YOUR.JWT.HERE'
    payload = token.split('.')[1]
    # Add padding if needed
    payload += '=' * (4 - len(payload) % 4)
    print(json.loads(base64.urlsafe_b64decode(payload)))
    "
    Learn more

    Base64url is a URL-safe variant of base64 that replaces + with - and / with _ and omits padding =. You may need to add padding back when decoding manually.

  3. Step 3Crack the HMAC secret or use 'none' algorithm
    Try two approaches: (1) Change the algorithm to 'none' and remove the signature. (2) Use hashcat or jwt_tool to crack the weak HMAC secret (often 'ilovepico' or 'secret'). Then forge a new token signed with the cracked secret.
    python
    python3 -m pip install jwt
    python
    python3 -c "
    import jwt
    # Attempt 1: none algorithm
    header = {'alg': 'none', 'typ': 'JWT'}
    payload = {'user': 'admin'}
    # Manually construct: base64(header).base64(payload).
    import base64, json
    h = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b'=').decode()
    p = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b'=').decode()
    print(f'{h}.{p}.')
    "
    bash
    hashcat -a 0 -m 16500 <jwt_token> /usr/share/wordlists/rockyou.txt
    Learn more

    The 'none' algorithm attack works on JWT libraries that accept unsigned tokens without validation. The attacker sets alg: none in the header and omits the signature. Many older libraries trusted this.

    If the server validates the algorithm, the next attack is cracking the HMAC secret. HS256 JWTs use HMAC-SHA256 with a shared secret. If the secret is weak (a dictionary word), hashcat with -m 16500 (JWT mode) can recover it from a wordlist.

    Once you have the secret, use PyJWT: jwt.encode({"user": "admin"}, secret, algorithm="HS256") to forge a valid admin token.

  4. Step 4Set the forged JWT and access admin page
    Replace your JWT cookie with the forged admin token using DevTools (Application > Cookies). Reload the page to see the flag.
    Learn more

    JWT vulnerabilities arise from weak secrets, accepting the 'none' algorithm, or algorithm confusion attacks (e.g., confusing RS256 with HS256). Always use strong random secrets and explicitly validate the expected algorithm on the server side.

Alternate Solution

Use the JWT Decoder tool on this site to decode and inspect the token's header and payload in one click. Once you confirm the payload claims, forge the modified token using jwt.io's built-in editor (paste your token, swap the algorithm to none, and edit the payload).

Flag

picoCTF{...}

Forge a JWT with user=admin by either cracking the weak HMAC secret (try 'ilovepico') or exploiting the 'none' algorithm.

Want more picoCTF 2019 writeups?

Useful tools for Web Exploitation

Related reading

What to try next