The JWT That Accepted the none Algorithm
The verifier read the algorithm from the token header and honoured it, so a token signed by nobody was accepted as valid, and the header was the attacker's…
The protected behaved. The header behaved. The chain between them is where working as designed stopped meaning working.
I keep a private list of these, because the incidents that hurt are always the ones somebody assumed were harmless.
The algorithm confusion has a single weak property, and here it is: the token declares how it should be verified, and the verifier obeys the declaration, so the attacker chooses the verification method, and the choice is the none.
This was a JWT library used with an explicit decode, and every verifier that reads the algorithm from the unverified header behaves the same way.
Why the header was trusted
The JWT is the three parts, and the three are the header and the payload and the signature, and the header is the algorithm's declaration, and the declaration is the base64, and the base64 is the unverified, and the unverified is the attacker's, and the attacker's is the modification, and the modification is the none, and the none is the verification's skip, and the skip is the accepted.
The verifier read the header first, and the first was the algorithm, and the algorithm was the method, and the method was the none, and the none was the no check, and the no check was the valid, and the valid was the payload, and the payload was the admin, and the admin was the attacker's claim, and the claim was the trusted, and the trusted was the breach.
The trust is the order's error, because the header is the unverified input, and the unverified is the not the instruction, and the not instruction is the rule, and the rule is the verifier's algorithm, and the verifier's is the configured, and the configured is the fix, and the fix is the ignore the header's claim.
Why the none exists
The none is the specification's, and the specification's is the unsecured JWT, and the unsecured is the test's case, and the test's is the legitimate, and the legitimate is the library's support, and the support is the availability, and the availability is the attacker's option, and the option is the selection, and the selection is the header's, and the header's is the bypass.
The none is not the only confusion, and the not only is the RS256 to HS256, and the HS256 is the symmetric, and the symmetric is the public key as the secret, and the secret is the known, and the known is the forged signature, and the forged is the valid, and the valid is the same class, and the class is the algorithm's confusion, and the confusion is the header's trust.
This is the same borrowed authority as the CORS wildcard that echoed the request's origin, and the shared property is the one worth fixing.
The fixes
Pin the algorithm in the verifier. The verification specifies the expected algorithm, and the expected is the configured, and the configured is the header's override, and the override is the fix, and the fix is the one option, and the option is the library's parameter, and the parameter is the discipline, because the verifier's algorithm is the server's decision and not the token's.
Reject the none algorithm explicitly. The none is the rejected, and the rejected is the error, and the error is the loud, and the loud is the fix, and the fix is the check, and the check is the library's setting, and the setting is the default's override, and the override is the discipline, because the default in some libraries permits the unsecured token.
Never use the public key as an HMAC secret. The RS256 public key is the known, and the known is the HS256 secret, and the secret is the forge, and the forge is the breach, and the breach is the fix's target, and the target is the single algorithm, and the single is the pinning, and the pinning is the prevention.
Validate the payload's claims, not only the signature. The exp and the iss and the aud are the checks, and the checks are the token's scope, and the scope is the limit, and the limit is the fix, and the fix is the validation, and the validation is the discipline, because the valid signature on the wrong audience is the misuse.
Log the rejected tokens. The rejected is the attempt, and the attempt is the attacker's probe, and the probe is the detection, and the detection is the fix, and the fix is the alert, and the alert is the discipline, because the algorithm confusion attempt is the visible in the log and the invisible in the traffic.
The rule
A JWT's header is unverified input, so a verifier that reads the algorithm from it lets the attacker choose the verification method, including none. Pin the expected algorithm in the verifier, reject none explicitly, never use a public key as an HMAC secret, validate the payload claims, and log the rejections.
The admin API accepted a token with no signature at all, because the verifier asked the token how it should be verified and the token said none. Name that one property in review and this class of bug gets hard to ship.
Published on debugly.dev by Rohit Bhadani | Founder of Krova Cloud