Input Validation in multipart header parsing
Description
The commit fixes a bug in multipart header parsing where the parser could be fed the entire header line (including the header name) instead of just the header value. This could lead to incorrect parsing or misinterpretation of header parameters for multipart requests. The change tightens input handling by splitting the header line at the first colon and only passing the value portion to parse_header_parameters, ensuring the header name is not mis-parsed as part of the value. A test asserting proper handling of malformed headers (e.g., spaces around the colon) was added to validate robust parsing.
Commit Details
Author: Jake Howard
Date: 2025-08-20 15:04 UTC
Message:
Refs #36520 -- Ensured only the header value is passed to parse_header_parameters for multipart requests.
Header parsing should apply only to the header value. The previous
implementation happened to work but relied on unintended behavior.
Triage Assessment
Vulnerability Type: Input Validation
Confidence: MEDIUM
Reasoning:
The change ensures that only the header value is passed to parse_header_parameters for multipart headers, correcting a bug where the entire header line could be fed into the parser. This tightens input handling in multipart header parsing and could prevent misinterpretation or exploitation of crafted headers, which is consistent with security-related input validation fixes.
Verification Assessment
Vulnerability Type: Input Validation in multipart header parsing
Confidence: MEDIUM
Affected Versions: 5.1.x before commit 41ff30f6f9d072036be1f74db8f0c8b21565299f
Code Diff
diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
index 531f9a046805..d420c255eb19 100644
--- a/django/http/multipartparser.py
+++ b/django/http/multipartparser.py
@@ -721,11 +721,10 @@ def parse_boundary_stream(stream, max_header_size):
# Eliminate blank lines
for line in header.split(b"\r\n"):
- # This terminology ("main value" and "dictionary of
- # parameters") is from the Python docs.
try:
- main_value_pair, params = parse_header_parameters(line.decode())
- name, value = main_value_pair.split(":", 1)
+ header_name, value_and_params = line.decode().split(":", 1)
+ name = header_name.lower().rstrip(" ")
+ value, params = parse_header_parameters(value_and_params.lstrip(" "))
params = {k: v.encode() for k, v in params.items()}
except ValueError: # Invalid header.
continue
diff --git a/tests/requests_tests/tests.py b/tests/requests_tests/tests.py
index 7e615617d7bf..36843df9b65b 100644
--- a/tests/requests_tests/tests.py
+++ b/tests/requests_tests/tests.py
@@ -450,6 +450,34 @@ def test_body_after_POST_multipart_form_data(self):
with self.assertRaises(RawPostDataException):
request.body
+ def test_malformed_multipart_header(self):
+ for header in [
+ 'Content-Disposition : form-data; name="name"',
+ 'Content-Disposition:form-data; name="name"',
+ 'Content-Disposition :form-data; name="name"',
+ ]:
+ with self.subTest(header):
+ payload = FakePayload(
+ "\r\n".join(
+ [
+ "--boundary",
+ header,
+ "",
+ "value",
+ "--boundary--",
+ ]
+ )
+ )
+ request = WSGIRequest(
+ {
+ "REQUEST_METHOD": "POST",
+ "CONTENT_TYPE": "multipart/form-data; boundary=boundary",
+ "CONTENT_LENGTH": len(payload),
+ "wsgi.input": payload,
+ }
+ )
+ self.assertEqual(request.POST, {"name": ["value"]})
+
def test_body_after_POST_multipart_related(self):
"""
Reading body after parsing multipart that isn't form-data is allowed