[Python] How can I trigger CVE-2024-30251 (aiohttp multipart/form-data DoS) with a minimal POC...

Discussão em 'Python' iniciado por Stack, Julho 11, 2025.

  1. Stack

    Stack Membro Participativo

    I am trying to reproduce CVE-2024-30251, a denial-of-service vulnerability in aiohttp (Python async web framework) that affects versions prior to 3.9.4. According to the advisories, this vulnerability is triggered by sending a specially crafted multipart/form-data POST request, which causes the aiohttp server to enter an infinite loop and become unresponsive, effectively resulting in a DoS.

    What I’ve tried

    • I have set up a minimal aiohttp server and tested with both version 3.8.6 (known to be affected) and 3.9.5 (patched).
    • I crafted various malformed multipart payloads, including missing closing boundaries and using large Content-Length headers.
    • I also tested nested multipart content and abrupt connection closures.
    Observed behavior

    • In both 3.8.6 and 3.9.5, the server does not hang. Instead, I consistently get a handled error such as "Exception during upload handling: Reading after EOF" or a 400 Bad Request.
    • The server never enters an infinite loop or becomes unresponsive, even with malformed or incomplete multipart payloads.
    My question

    • What is the exact payload and connection behavior required to reliably trigger the infinite loop/DoS described in CVE-2024-30251 in aiohttp 3.8.6?
    • Are there any environment-specific factors (e.g., OS, Python version, buffering) that are required for this to manifest?

    Has anyone been able to reproduce this in a minimal exploit script format, and if so, could you share the precise steps and payload?

    Simple Server and Client

    • server.py


    import logging
    from aiohttp import web

    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger("multipart-server")

    async def upload_handler(request):
    logger.info("Received upload request.")
    try:
    reader = await request.multipart()
    logger.info("Initialized multipart reader.")
    part_num = 0
    while True:
    logger.info("Waiting for next part...")
    part = await reader.next()
    if part is None:
    logger.info("No more parts. Done reading multipart data.")
    break
    part_num += 1
    logger.info(f"Reading part {part_num}: headers={dict(part.headers)}")
    chunk_num = 0
    while True:
    logger.info(f"Reading chunk {chunk_num} from part {part_num}...")
    chunk = await part.read_chunk()
    if not chunk:
    logger.info(f"End of part {part_num}.")
    break
    logger.info(f"Read chunk {chunk_num} of size {len(chunk)} bytes from part {part_num}.")
    chunk_num += 1
    logger.info("Finished reading all parts.")
    return web.Response(text="Upload processed successfully.")
    except Exception as e:
    logger.error(f"Exception during upload handling: {e}")
    return web.Response(text=f"Exception: {e}", status=400)

    app = web.Application()
    app.router.add_post('/upload', upload_handler)

    if __name__ == "__main__":
    logger.info("Starting aiohttp server on http://127.0.0.1:8080 ...")
    web.run_app(app, port=8080)

    • client.py



    import socket

    host = '127.0.0.1'
    port = 8080

    boundary = '----MALFORMED_BOUNDARY'
    body = (
    f'--{boundary}\r\n'
    'Content-Disposition: form-data; name="field1"\r\n'
    '\r\n'
    'value1\r\n'
    f'--{boundary}\r\n'
    'Content-Disposition: form-data; name="file"; filename="test.txt"\r\n'
    'Content-Type: text/plain\r\n'
    '\r\n'
    'This is the file content.\r\n'
    # No closing boundary!
    )

    headers = (
    f'POST /upload HTTP/1.1\r\n'
    f'Host: {host}:{port}\r\n'
    f'Content-Type: multipart/form-data; boundary={boundary}\r\n'
    f'Content-Length: {len(body.encode("utf-8"))}\r\n'
    f'Connection: close\r\n'
    '\r\n'
    )

    with socket.create_connection((host, port)) as sock:
    sock.sendall(headers.encode('utf-8') + body.encode('utf-8'))
    try:
    response = sock.recv(4096)
    print("Server responded:")
    print(response.decode('utf-8', errors='replace'))
    except Exception as e:
    print(f"Exception while reading response: {e}")


    References


    GitHub Security Advisory

    NVD CVE-2024-30251

    Summary


    Despite following public advisories and trying malformed multipart requests, I see the same error handling in both affected and fixed aiohttp versions. I would greatly appreciate a concrete, minimal POC script that demonstrates the actual DoS/infinite loop described in the CVE, such that it enters the loop in affected version (3.8.6) and handles gracefully in patched version (according to advisory)(>=3.9.4)

    Thank you for any guidance or working examples!

    Continue reading...

Compartilhe esta Página