VYPR
High severityNVD Advisory· Published Feb 5, 2024· Updated May 9, 2025

python-multipart vulnerable to content-type header Regular expression Denial of Service

CVE-2024-24762

Description

python-multipart is a streaming multipart parser for Python. When using form data, python-multipart uses a Regular Expression to parse the HTTP Content-Type header, including options. An attacker could send a custom-made Content-Type option that is very difficult for the RegEx to process, consuming CPU resources and stalling indefinitely (minutes or more) while holding the main event loop. This means that process can't handle any more requests, leading to regular expression denial of service. This vulnerability has been patched in version 0.0.7.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
python-multipartPyPI
< 0.0.70.0.7

Affected products

3

Patches

3
13e5c26a27f4

Merge pull request from GHSA-93gm-qmq6-w238

https://github.com/encode/starletteSebastián RamírezFeb 3, 2024via ghsa
1 file changed · +1 1
  • pyproject.toml+1 1 modified
    @@ -36,7 +36,7 @@ dependencies = [
     full = [
         "itsdangerous",
         "jinja2",
    -    "python-multipart",
    +    "python-multipart>=0.0.7",
         "pyyaml",
         "httpx>=0.22.0",
     ]
    
9d34ad0ee8a0

Merge pull request from GHSA-qf9m-vfgh-m389

https://github.com/tiangolo/fastapiSebastián RamírezFeb 3, 2024via ghsa
2 files changed · +2 2
  • pyproject.toml+1 1 modified
    @@ -56,7 +56,7 @@ Repository = "https://github.com/tiangolo/fastapi"
     all = [
         "httpx >=0.23.0",
         "jinja2 >=2.11.2",
    -    "python-multipart >=0.0.5",
    +    "python-multipart >=0.0.7",
         "itsdangerous >=1.1.0",
         "pyyaml >=5.3.1",
         "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0",
    
  • requirements-tests.txt+1 1 modified
    @@ -13,7 +13,7 @@ sqlalchemy >=1.3.18,<1.4.43
     databases[sqlite] >=0.3.2,<0.7.0
     orjson >=3.2.1,<4.0.0
     ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0
    -python-multipart >=0.0.5,<0.0.7
    +python-multipart >=0.0.7,<0.1.0
     flask >=1.1.2,<3.0.0
     anyio[trio] >=3.2.1,<4.0.0
     python-jose[cryptography] >=3.3.0,<4.0.0
    
20f0ef6b4e4c

♻️ Refactor header option parser to use the standard library instead of a custom RegEx (#75)

https://github.com/Kludex/python-multipartSebastián RamírezFeb 3, 2024via ghsa
1 file changed · +26 24
  • multipart/multipart.py+26 24 modified
    @@ -9,6 +9,8 @@
     import tempfile
     from io import BytesIO
     from numbers import Number
    +from email.message import Message
    +from typing import Dict, Union, Tuple
     
     # Unique missing object.
     _missing = object()
    @@ -76,44 +78,44 @@
     QUOTE = b'"'[0]
     
     
    -def parse_options_header(value):
    +def parse_options_header(value: Union[str, bytes]) -> Tuple[bytes, Dict[bytes, bytes]]:
         """
         Parses a Content-Type header into a value in the following format:
             (content_type, {parameters})
         """
    +    # Uses email.message.Message to parse the header as described in PEP 594.
    +    # Ref: https://peps.python.org/pep-0594/#cgi
         if not value:
             return (b'', {})
     
    -    # If we are passed a string, we assume that it conforms to WSGI and does
    -    # not contain any code point that's not in latin-1.
    -    if isinstance(value, str):            # pragma: no cover
    -        value = value.encode('latin-1')
    +    # If we are passed bytes, we assume that it conforms to WSGI, encoding in latin-1.
    +    if isinstance(value, bytes):  # pragma: no cover
    +        value = value.decode('latin-1')
    +
    +    # For types
    +    assert isinstance(value, str), 'Value should be a string by now'
     
         # If we have no options, return the string as-is.
    -    if b';' not in value:
    -        return (value.lower().strip(), {})
    +    if ";" not in value:
    +        return (value.lower().strip().encode('latin-1'), {})
     
         # Split at the first semicolon, to get our value and then options.
    -    ctype, rest = value.split(b';', 1)
    +    # ctype, rest = value.split(b';', 1)
    +    message = Message()
    +    message['content-type'] = value
    +    params = message.get_params()
    +    # If there were no parameters, this would have already returned above
    +    assert params, 'At least the content type value should be present'
    +    ctype = params.pop(0)[0].encode('latin-1')
         options = {}
    -
    -    # Parse the options.
    -    for match in OPTION_RE.finditer(rest):
    -        key = match.group(1).lower()
    -        value = match.group(2)
    -        if value[0] == QUOTE and value[-1] == QUOTE:
    -            # Unquote the value.
    -            value = value[1:-1]
    -            value = value.replace(b'\\\\', b'\\').replace(b'\\"', b'"')
    -
    +    for param in params:
    +        key, value = param
             # If the value is a filename, we need to fix a bug on IE6 that sends
             # the full file path instead of the filename.
    -        if key == b'filename':
    -            if value[1:3] == b':\\' or value[:2] == b'\\\\':
    -                value = value.split(b'\\')[-1]
    -
    -        options[key] = value
    -
    +        if key == 'filename':
    +            if value[1:3] == ':\\' or value[:2] == '\\\\':
    +                value = value.split('\\')[-1]
    +        options[key.encode('latin-1')] = value.encode('latin-1')
         return ctype, options
     
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

12

News mentions

0

No linked articles in our index yet.