Input Validation in multipart header parsing

MEDIUM
django/django
Commit: 41ff30f6f9d0
Affected: 5.1.x before commit 41ff30f6f9d072036be1f74db8f0c8b21565299f
2026-04-05 13:35 UTC

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
← Back to Alerts View on GitHub →