Skip to content
Lesson 8 of 14

Step 1 of 5 · Reading · ~3 min

Read

Production Concerns

Known JWT Attacks

Every JWT break has one shape. RFC 7519 §11.1: a token can be relied on only when its contents are cryptographically secured and the key is verifiably controlled by the issuer named in it. Break either half and the token says whatever the attacker wants.

1. Algorithm confusion: turning the public key into the secret

This one gets real systems, because it attacks a verifier doing everything else right. Picture a service verifying RS256 tokens from an identity provider, holding one thing: the provider's public key — published, because that is how verification works. Watch a naive verifier:

python

Change alg from RS256 to HS256: dispatch lands on the HMAC branch, where key — the RSA public key — becomes a shared secret the attacker already downloaded. So they sign for themselves:

python

That token verifies. No key stolen, no cryptography broken — the verifier was persuaded to use a public value as a private one.

The defense is not "check the alg" but to stop letting the token into the decision:

python

Bind the algorithm to the key, not the header. If your code can hand a public key to an HMAC function, the bug is the shape of your code.

2. The none algorithm

Rewrite the header to {"alg":"none"} and delete the signature. RFC 7519 §6 really does define this: an Unsecured JWT is alg of "none" with the empty string as its signature, so a genuine one ends in a bare dot. Same root cause, same fix; it has a lesson of its own.

3. Guessable HMAC secrets

secret, changeme, your-256-bit-secret. No server access needed: capture a token and grind a wordlist offline, with no rate limit. Cracking tools ship JWT modes for this (hashcat's 16500).

4. Tokens in URLs

GET /report?token=eyJ... writes a live credential into browser history, access logs, proxy logs, and the Referer header.

5. Long lifetimes

A stateless token cannot be revoked, only expire — so exp is your whole revocation story and the lifetime you pick is the attacker's window. Minutes, not months.

6. Sensitive data in the payload

Base64 is not encryption. Card numbers, national IDs and password hashes have all shipped in production JWTs, readable by anyone holding one.

Summary

AttackRoot causeDefense
Algorithm confusiontoken picks the algorithmpin it, bound to key type
alg: nonetoken picks no algorithmreject none, any casing
Weak secretkey chosen by a human32+ CSPRNG bytes
Token in URLcredential somewhere loggableAuthorization
Long expexpiry is the only revocationshort exp + refresh
Data in payloadencoding taken for encryptionstore an id

Your exercise

You will write a linter: one token per line, issues comma-separated or OK. Two things the grader is strict about:

  • Order. A token both unsigned and unexpiring prints exactly alg_none,no_expiration. Issues emit as alg_none, no_expiration, long_lived, pii_in_payload — not the order you detect them.
  • long_lived needs both endpoints. exp - iat > 86400, so a payload with exp and no iat has no computable lifetime and must not be flagged: the visible {"sub":"x","password":"hunter2","exp":3600} is pii_in_payload only.

The third segment is a placeholder — the linter never inspects it.

Up nextPutting It All TogetherProduction Concerns

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…