Web › Module 3 › Lesson 3
JWT Vulnerabilities
Decode JWT structure, spot alg:none and weak secrets, and learn why “stateless” is not “safe by default”
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 algsFix: 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:
passPrefer 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
A JWT payload is:
Multiple choice
Knowledge Check
True or False: Accepting alg:none without signature verification is a critical JWT flaw.
True or False
Knowledge Check
Against HS256 tokens, a weak shared secret enables:
Multiple choice