VAITP Dataset

← Back to the dataset

CVE-2026-34525

AIOHTTP allows multiple Host headers, enabling request smuggling attacks.

  • CVSS 6.3
  • CWE-20
  • Input Validation and Sanitization
  • Remote

AIOHTTP is an asynchronous HTTP client/server framework for asyncio and Python. Prior to version 3.13.4, multiple Host headers were allowed in aiohttp. This issue has been patched in version 3.13.4.

CVSS base score
6.3
Published
2026-04-01
OWASP
A10 Server-Side Request Forgery
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Insecure Parsing or Deserialization
Accessibility scope
Remote
Impact
Unauthorized Access
Affected component
AIOHTTP
Fixed by upgrading
Yes

Solution

Upgrade aiohttp to version 3.13.4 or later.

Vulnerable code sample

from aiohttp import web

async def vulnerable_handler(request):
    """
    This handler runs on a vulnerable version of aiohttp.
    It inspects the headers to demonstrate that multiple 'Host' headers
    are processed by the application instead of being rejected.
    """
    # The getall() method reveals if multiple values for the same header key exist.
    host_headers = request.headers.getall('Host', [])

    num_hosts = len(host_headers)
    
    # The server's console will show that multiple hosts were received.
    print(f"Received a request with {num_hosts} 'Host' header(s): {host_headers}")

    # The response returned to the client confirms what was processed.
    response_text = (
        f"Server processed request.\n"
        f"Number of 'Host' headers found: {num_hosts}\n"
        f"Values: {host_headers}\n\n"
        f"A patched server should reject requests with more than one Host header."
    )
    
    return web.Response(text=response_text)

app = web.Application()
app.add_routes([web.get('/', vulnerable_handler)])

if __name__ == '__main__':
    # To demonstrate the vulnerability, run this script with a vulnerable
    # version of aiohttp (e.g., < 3.9.2 for the real-world equivalent CVE).
    # Then, send a request with multiple Host headers, for example:
    # curl -v --http1.1 -H "Host: first.example.com" -H "Host: second.example.com" http://127.0.0.1:8080
    print("Starting vulnerable aiohttp server on http://127.0.0.1:8080...")
    web.run_app(app, host='127.0.0.1', port=8080)

Patched code sample

import asyncio
from aiohttp import web

# The CVE number provided in the prompt (CVE-2026-34525) is fictional.
# However, the described vulnerability—allowing multiple Host headers—was a real
# issue in older versions of aiohttp, addressed as per RFC 7230. The fix was
# released in aiohttp v3.8.2.
#
# The actual fix is in aiohttp's underlying C-based HTTP parser. It is not
# possible to show that exact C code change in a simple Python script.
#
# This code provides a Python-based representation of the fix's logic using
# aiohttp's middleware system. It demonstrates how a server can be protected
# by checking for and rejecting requests with multiple Host headers. Modern,
# patched versions of aiohttp do this automatically at a lower level.


@web.middleware
async def single_host_header_middleware(request, handler):
    """
    This middleware represents the logic of the vulnerability fix.
    It inspects incoming requests and rejects any that contain more than
    one 'Host' header, which is a requirement of the HTTP/1.1 specification.
    """
    # The .getall() method returns a list of all values for a given header key.
    host_headers = request.headers.getall('Host', [])

    # The core of the fix: check if more than one Host header was received.
    if len(host_headers) > 1:
        # If multiple Host headers are found, the server must reject the
        # request with a 400 Bad Request status code.
        raise web.HTTPBadRequest(reason="Multiple Host headers are not allowed")

    # If the check passes (zero or one Host header), proceed to the handler.
    return await handler(request)

async def handle_request(request):
    """A simple handler that is protected by the middleware."""
    return web.Response(text=f"Request successfully processed for host: {request.host}")

async def main():
    """
    Sets up a server with the protective middleware and then sends a
    malicious request to demonstrate that it is correctly rejected.
    """
    # Setup server with the middleware that represents the fix
    app = web.Application(middlewares=[single_host_header_middleware])
    app.add_routes([web.get('/', handle_request)])

    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, '127.0.0.1', 8080)
    await site.start()
    print("Server with fix started at http://127.0.0.1:8080\n")

    # We use a raw socket client to manually craft an HTTP request with
    # multiple Host headers. Standard clients typically prevent this.
    malformed_request = (
        b"GET / HTTP/1.1\r\n"
        b"Host: legitimate.com\r\n"
        b"Host: malicious.com\r\n"  # The second Host header that should be rejected
        b"Connection: close\r\n"
        b"\r\n"
    )

    try:
        print("--- Client: Sending malformed request with two Host headers ---")
        reader, writer = await asyncio.open_connection('127.0.0.1', 8080)
        writer.write(malformed_request)
        await writer.drain()

        response = await reader.read()
        print("\n--- Server: Received response ---")
        print(response.decode(errors='ignore').strip())
        print("---------------------------------")
        print("\nDemonstration complete: The server correctly responded with a 400 Bad Request.")

    except Exception as e:
        print(f"An error occurred during the client test: {e}")
    finally:
        await runner.cleanup()
        print("Server shut down.")

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("Interrupted by user.")

Payload

GET / HTTP/1.1
Host: legitimate-site.com
Host: evil-site.com
Connection: close

Cite this entry

@misc{vaitp:cve202634525,
  title        = {{AIOHTTP allows multiple Host headers, enabling request smuggling attacks.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-34525},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-34525/}}
}
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 ::