DEV Community

Rocky
Rocky

Posted on

The JWT Said alg: none and the API Still Said Yes

Mid-assessment on an internal API, every endpoint gated behind a JWT in the Authorization header. Nothing about the token looked unusual: decode it in Burp's inspector, standard header, a role claim sitting right there in plain base64. Out of habit more than expectation, the header got rewritten to {"alg":"none","typ":"JWT"}, the signature segment got deleted entirely so the token ended in a trailing dot with nothing after it, and the request went out anyway. The API answered with a normal 200 and the same data a properly signed token would have returned. The role claim was still just an untouched string sitting in the payload. It got changed to admin and sent again. Same result.

That works because a surprising number of JWT verification implementations check whatever algorithm the token itself claims to be using, instead of pinning to one expected algorithm chosen by the server. Tell a library "verify this signature" without also telling it "and reject anything that isn't RS256," and some of them will happily honor alg: none and skip the signature check altogether, because as far as the code is concerned, no algorithm means nothing to verify. A close cousin of this is algorithm confusion: a server that verifies with RS256 using a public key can sometimes be tricked into treating that same public key as an HMAC secret if an attacker resubmits the token signed with HS256 instead, since a public key is, mechanically, just bytes a library can HMAC against if nothing stops it from trying.

When alg: none gets rejected, the fallback isn't cleverness, it's brute force aimed at the right target. Any token signed with HS256 is only as strong as the secret used to sign it, and plenty of real implementations use a secret that's short, a default from a tutorial nobody rotated, or reused across services. Tools built for exactly this, hashcat's dedicated JWT mode or jwt_tool's cracking flag, take the token and a wordlist and grind through candidate secrets until one produces a matching signature. Once that secret comes back, it doesn't just confirm a weakness: it hands over the ability to sign any payload, with any claim, as a token the server will accept as fully legitimate.

None of this requires exploit development. It requires three checks run in order on every JWT a target hands out: does it accept none, does it confuse RS256 for HS256 when asked to, and is the signing secret weak enough to crack offline. Skip that checklist and a huge share of an API's real access control, the part that decides who gets to claim they're an admin, never actually gets tested.

Codelivly's API Hacking book walks through all three of these JWT attack paths end to end as part of its broader BOLA, OAuth and API auth coverage, and the JWT alg:none lab and weak secret lab on the site let you run this against a real target instead of just reading about it.

Top comments (0)