Skip to content
Z3tra
All articles
2 min read

The JWT mistakes that still work in 2026

Algorithm confusion, unverified signatures and secrets that were never secret. A tour of the JWT failures I keep finding in labs, and what each one actually requires to exploit.

JSON Web Tokens are not insecure. Almost every JWT vulnerability is a library being used in a way the specification technically permits and no sensible person wanted.

Here are the ones I still reproduce in labs, in roughly the order I check them.

1. The signature is never verified

The most common and the least interesting. The application decodes the token and reads the claims without calling the verification function at all.

// This decodes. It does not verify. They are different functions and the
// naming does not help.
const claims = jwt.decode(token);
if (claims.role === "admin") { /* ... */ }

Test: change a claim, re-encode without touching the signature, send it. If it is accepted, the signature was decoration.

2. alg: none

The specification defines an "unsecured" JWS with the algorithm none and an empty signature. Some libraries used to honour it by default.

{ "alg": "none", "typ": "JWT" }

Mostly dead in maintained libraries, still alive in pinned dependencies and hand-rolled parsers. Cheap to test, so always worth the thirty seconds.

3. Algorithm confusion: RS256 to HS256

This one is genuinely elegant, which is why it keeps working.

The server issues tokens signed with RS256 and verifies them with its public key. An attacker changes the header to HS256 and signs the token using that same public key as the HMAC secret. A verification function that picks its algorithm from the token header will now happily HMAC-verify with a key everyone can read.

The root cause is the same as none: trusting the token to describe how it should be checked.

// The bug: the algorithm comes from attacker-controlled input.
jwt.verify(token, publicKey);
 
// The fix: the server decides, not the token.
jwt.verify(token, publicKey, { algorithms: ["RS256"] });

Getting the public key is usually trivial — a /.well-known/jwks.json, a certificate, or the key material published alongside the API docs. It is public. That is the point of it.

4. Weak HMAC secrets

If the token is HS256 and the secret is a word, it is recoverable offline. No requests to the target, no rate limit, no logs.

# Against a token from my own lab.
hashcat -a 0 -m 16500

Related

Article3 min

Understand the application before you test it

Scanners find what they were told to look for. The bugs that matter live in the gap between what an application believes about itself and what it actually enforces.

  • Web Security
  • OWASP
  • Methodology
Article4 min

IDOR is still everywhere, and here is why

Broken object level authorisation stays the most common serious finding in web applications. Not because it is hard to fix, but because of where the check has to live.

  • Web Security
  • OWASP
  • Bug Bounty
Write-up3 min

The JWT that trusted its own header

Personal lab

A lab write-up on an authentication bypass through JWT algorithm confusion — the classic 'alg: none' and its slightly less obvious RS256-to-HS256 cousin.

  • Write-ups
  • Web Security
  • Authentication