C

Web › Module 3 › Lesson 3

BeginnerModule 3Lesson 3/6

JWT Vulnerabilities

Decode JWT structure, spot alg:none and weak secrets, and learn why “stateless” is not “safe by default”

15 min+56 XP3 quiz
Module progress3 of 6

Visual · web_developer

A JSON Web Token carries claims like user ID and role. If verification is sloppy, attackers forge tokens or swap algorithms to become admin.

Opening

Three Base64 chunks—one bad verify step

JWTs look opaque, but they are mostly Base64URL-encoded JSON: header, payload, signature. Developers love them for microservices and mobile APIs—but misconfigured libraries turn them into forged VIP passes. We will study classic mistakes (alg:none, weak HMAC secrets) so you can fix them in your own lab—not abuse production systems.

1. JWT Anatomy

Format: header.payload.signature • Header — usually alg (algorithm) and typ • Payload — claims such as sub, exp, role • Signature — proves integrity if the server verifies with the correct key Important: the payload is not encrypted. Anyone with the token can read it. Do not put secrets inside.

2. The alg:none Attack (Educational)

Vulnerable verify logic (never ship this)# Attacker changes header to {"alg":"none","typ":"JWT"} # Removes signature, keeps payload: {"sub":"admin","role":"admin"} def bad_verify(token): header, payload, sig = token.split(".") alg = json.loads(b64decode(header))["alg"] if alg == "none": return json.loads(b64decode(payload)) # accepts unsigned! # ... real verify for other algs

# Attacker changes header to {"alg":"none","typ":"JWT"}
# Removes signature, keeps payload: {"sub":"admin","role":"admin"}

def bad_verify(token):
    header, payload, sig = token.split(".")
    alg = json.loads(b64decode(header))["alg"]
    if alg == "none":
        return json.loads(b64decode(payload))  # accepts unsigned!
    # ... real verify for other algs

Fix: reject alg:none explicitly, use an allowlist of algorithms, and never trust the header’s alg without matching your configured key type.

3. Weak HMAC Secrets

Lab-only demo — guessing a short secret offlineimport jwt, itertools token = "eyJ...your_lab_token..." # from YOUR local app wordlist = ["secret", "password", "cyberlium", "changeme"] for guess in wordlist: try: jwt.decode(token, guess, algorithms=["HS256"]) print("Weak secret found:", guess) break except jwt.InvalidSignatureError: pass

import jwt, itertools

token = "eyJ...your_lab_token..."  # from YOUR local app
wordlist = ["secret", "password", "cyberlium", "changeme"]

for guess in wordlist:
    try:
        jwt.decode(token, guess, algorithms=["HS256"])
        print("Weak secret found:", guess)
        break
    except jwt.InvalidSignatureError:
        pass

Prefer asymmetric keys in production

Use RS256/ES256 with a private key on the issuer and public key for verifiers. Rotate keys, set short exp, validate iss/aud, and store refresh tokens securely—not in localStorage for high-risk apps.

Knowledge Check

1

A JWT payload is:

Multiple choice

Knowledge Check

2

True or False: Accepting alg:none without signature verification is a critical JWT flaw.

True or False

Knowledge Check

3

Against HS256 tokens, a weak shared secret enables:

Multiple choice

← Previous

Answer all 3 knowledge checks to continue. (0/3 answered)