CVE-2024-30251: Insufficient Information (NVD-CWE-noinfo)
value: aiohttp is an asynchronous HTTP client/server framework for asyncio and Python. In affected versions an attacker can send a specially crafted POST (multipart/form-data) request. When the aiohttp server processes it, the server will enter an infinite loop and be unable to process any further requests. An attacker can stop the application from serving requests after sending a single request. This issue has been addressed in version 3.9.4. Users are advised to upgrade. Users unable to upgrade may manually apply a patch to their systems. Please see the linked GHSA for instructions.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-05-02T14:15Z
Repository: https://github.com/aio-libs/aiohttp
Language: Python
Files changed: CHANGES/8332.bugfix.rst, aiohttp/multipart.py, tests/test_multipart.py
Commit date: 2024-04-15 20:47:19 +0100
Commit message: [PR #8332/482e6cdf backport][3.9] Add set_content_disposition test (#8333)

**This is a backport of PR #8332 as merged into master
(482e6cdf6516607360666a48c5828d3dbe959fbd).**

Co-authored-by: Oleg A <t0rr@mail.ru>
Diff: diff --git a/CHANGES/8332.bugfix.rst b/CHANGES/8332.bugfix.rst
new file mode 100644
index 0000000000..70cad26b42
--- /dev/null
+++ b/CHANGES/8332.bugfix.rst
@@ -0,0 +1 @@
+Fixed regression with adding Content-Disposition to form-data part after appending to writer -- by :user:`Dreamsorcerer`/:user:`Olegt0rr`.
diff --git a/aiohttp/multipart.py b/aiohttp/multipart.py
index a43ec54571..fcdf16183c 100644
--- a/aiohttp/multipart.py
+++ b/aiohttp/multipart.py
@@ -846,12 +846,10 @@ class MultipartWriter(Payload):
         encoding: Optional[str] = None
         te_encoding: Optional[str] = None
         if self._is_form_data:
             # https://datatracker.ietf.org/doc/html/rfc7578#section-4.7
             # https://datatracker.ietf.org/doc/html/rfc7578#section-4.8
-            assert CONTENT_DISPOSITION in payload.headers
-            assert "name=" in payload.headers[CONTENT_DISPOSITION]
             assert (
                 not {CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TRANSFER_ENCODING}
                 & payload.headers.keys()
             )
         else:
@@ -928,10 +926,15 @@ class MultipartWriter(Payload):
         return total
 
     async def write(self, writer: Any, close_boundary: bool = True) -> None:
         """Write body."""
         for part, encoding, te_encoding in self._parts:
+            if self._is_form_data:
+                # https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
+                assert CONTENT_DISPOSITION in part.headers
+                assert "name=" in part.headers[CONTENT_DISPOSITION]
+
             await writer.write(b"--" + self._boundary + b"\r\n")
             await writer.write(part._binary_headers)
 
             if encoding or te_encoding:
                 w = MultipartPayloadWriter(writer)
diff --git a/tests/test_multipart.py b/tests/test_multipart.py
index dbfaf74b9b..37ac54797f 100644
--- a/tests/test_multipart.py
+++ b/tests/test_multipart.py
@@ -1280,10 +1280,17 @@ class TestMultipartWriter:
         writer.append(subwriter, {CONTENT_TYPE: "test/passed"})
         assert 1 == len(writer)
         part = writer._parts[0][0]
         assert part.headers[CONTENT_TYPE] == "test/passed"
 
+    async def test_set_content_disposition_after_append(self):
+        writer = aiohttp.MultipartWriter("form-data")
+        payload = writer.append("some-data")
+        payload.set_content_disposition("form-data", name="method")
+        assert CONTENT_DISPOSITION in payload.headers
+        assert "name=" in payload.headers[CONTENT_DISPOSITION]
+
     def test_with(self) -> None:
         with aiohttp.MultipartWriter(boundary=":") as writer:
             writer.append("foo")
             writer.append(b"bar")
             writer.append_json({"baz": True})

Vulnerable code:             assert CONTENT_DISPOSITION in payload.headers
            assert "name=" in payload.headers[CONTENT_DISPOSITION]
Fixed code: Fixed regression with adding Content-Disposition to form-data part after appending to writer -- by :user:`Dreamsorcerer`/:user:`Olegt0rr`.
            if self._is_form_data:
                # https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
                assert CONTENT_DISPOSITION in part.headers
                assert "name=" in part.headers[CONTENT_DISPOSITION]

    async def test_set_content_disposition_after_append(self):
        writer = aiohttp.MultipartWriter("form-data")
        payload = writer.append("some-data")
        payload.set_content_disposition("form-data", name="method")
        assert CONTENT_DISPOSITION in payload.headers
        assert "name=" in payload.headers[CONTENT_DISPOSITION]