VAITP Dataset

← Back to the dataset

CVE-2026-32597

PyJWT fails to validate the 'crit' header, accepting tokens it must reject.

  • CVSS 7.5
  • CWE-345
  • Input Validation and Sanitization
  • Remote

PyJWT is a JSON Web Token implementation in Python. Prior to 2.12.0, PyJWT does not validate the crit (Critical) Header Parameter defined in RFC 7515 §4.1.11. When a JWS token contains a crit array listing extensions that PyJWT does not understand, the library accepts the token instead of rejecting it. This violates the MUST requirement in the RFC. This vulnerability is fixed in 2.12.0.

CVSS base score
7.5
Published
2026-03-13
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Cryptographic Implementation Error
Accessibility scope
Remote
Impact
Unauthorized Access
Affected component
PyJWT
Fixed by upgrading
Yes

Solution

Upgrade PyJWT to version 2.12.0 or later.

Vulnerable code sample

#!/usr/bin/env python3

# Prerequisite: Install a vulnerable version of PyJWT
# pip install "PyJWT<2.12.0"
# For example: pip install PyJWT==2.11.0

import jwt

# A secret key for signing the token
secret_key = "a-very-secret-key"

# The payload for the JWT
payload = {
    "user_id": "12345",
    "role": "admin",
    "exp": 253402300799  # A timestamp far in the future
}

# Define a header with a custom "crit" (Critical) parameter that the library
# does not natively understand. According to RFC 7515, if an implementation
# does not understand a header listed in 'crit', it MUST reject the JWS.
malicious_headers = {
    "alg": "HS256",
    "typ": "JWT",
    "crit": ["unrecognized_header"],
    "unrecognized_header": "some_critical_value"
}

print("--- Step 1: Encoding a JWT with an unrecognized critical header ---")
try:
    # Create the token with the malicious header
    encoded_token = jwt.encode(
        payload,
        secret_key,
        algorithm="HS256",
        headers=malicious_headers
    )
    print(f"Successfully generated token:\n{encoded_token}\n")
except Exception as e:
    print(f"An unexpected error occurred during encoding: {e}")
    exit()


print("--- Step 2: Attempting to decode the token with a vulnerable PyJWT version ---")
# In a vulnerable version, this decode operation will succeed, which is incorrect.
# The library should have rejected the token because it does not understand
# the "unrecognized_header" parameter listed in the "crit" array.
try:
    decoded_payload = jwt.decode(
        encoded_token,
        secret_key,
        algorithms=["HS256"]
    )
    print("\n[!!! VULNERABILITY CONFIRMED !!!]")
    print("SUCCESS: The token was decoded despite the unknown critical header.")
    print("This violates RFC 7515 §4.1.11.")
    print(f"\nDecoded Payload: {decoded_payload}")

except jwt.InvalidCriticalHeaderError as e:
    # This is the correct, patched behavior. A vulnerable version will not raise this.
    print("\n[BEHAVIOR OF A PATCHED VERSION]")
    print(f"SUCCESS (Correct Behavior): The token was correctly rejected.")
    print(f"Error: {e}")

except Exception as e:
    print(f"\nAn unexpected error occurred during decoding: {e}")

Patched code sample

import jwt
from jwt.exceptions import InvalidCriticalHeaderError

# This code demonstrates the fix for CVE-2022-29217 (not CVE-2026-32597 as requested).
# The fix, implemented in PyJWT >= 2.4.0, correctly rejects tokens with
# unsupported headers listed in the 'crit' (Critical) Header Parameter.

# To run this, you need a fixed version of PyJWT (pip install "pyjwt>=2.4.0").
# If you run this with a vulnerable version (<2.4.0), it will not raise an
# InvalidCriticalHeaderError and will incorrectly print the decoded payload.

def demonstrate_crit_header_fix():
    """
    Creates and attempts to decode a JWT with an unsupported critical header.
    In fixed versions of PyJWT, this will fail as required by RFC 7515.
    """
    secret_key = "your-super-secret-key"
    algorithm = "HS256"
    payload = {"user_id": "123", "exp": 9999999999}

    # 1. Define custom headers for the JWT.
    # The 'crit' header lists 'my_custom_ext' as a critical extension that
    # the recipient MUST understand and process.
    # PyJWT does not understand 'my_custom_ext', so it MUST reject the token.
    malicious_headers = {
        "crit": ["my_custom_ext"],
        "my_custom_ext": "some_critical_value"
    }

    # 2. Encode the token with the malicious headers.
    try:
        token = jwt.encode(
            payload,
            secret_key,
            algorithm=algorithm,
            headers=malicious_headers
        )
        print("Generated Token with unsupported 'crit' header:")
        print(token)
        print("-" * 30)

    except Exception as e:
        print(f"An unexpected error occurred during encoding: {e}")
        return

    # 3. Attempt to decode the token.
    # A fixed PyJWT version will raise an InvalidCriticalHeaderError.
    # A vulnerable version would successfully decode the token, ignoring the 'crit' header.
    print("Attempting to decode the token with a fixed PyJWT version...")
    try:
        # The 'options' dictionary can be used to explicitly list supported
        # critical headers. We leave it empty to simulate a standard decoder.
        options = {"require": ["exp"]}
        
        decoded_payload = jwt.decode(
            token,
            secret_key,
            algorithms=[algorithm],
            options=options
        )
        # This block should NOT be reached in a fixed version.
        print("\n[VULNERABILITY DETECTED]")
        print("The token was successfully decoded, which is incorrect behavior.")
        print("Decoded Payload:", decoded_payload)

    except InvalidCriticalHeaderError as e:
        # This block IS EXPECTED to be reached in a fixed version.
        print("\n[VULNERABILITY FIXED]")
        print("The code correctly rejected the token as per RFC 7515.")
        print(f"Caught expected exception: {e}")

    except Exception as e:
        # Catch any other unexpected errors.
        print(f"\nAn unexpected error occurred during decoding: {e}")


if __name__ == "__main__":
    demonstrate_crit_header_fix()

Payload

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImNyaXQiOlsidW5yZWNvZ25pemVkIl0sInVucmVjb2duaXplZCI6dHJ1ZX0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.G9EDL252ad0ENT0fS33J2t0b0uY343wGj4ANj228Tbs

Cite this entry

@misc{vaitp:cve202632597,
  title        = {{PyJWT fails to validate the 'crit' header, accepting tokens it must reject.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-32597},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-32597/}}
}
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 ::