CVE-2024-24762 (Python, published 2024-02-05T15:15Z)
Description: value: `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.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-1333 \u2014 Inefficient Regular Expression Complexity
The product uses a regular expression with an inefficient, possibly exponential worst-case computational complexity that consumes excessive CPU cycles.
Repository: https://github.com/Kludex/python-multipart
Files changed:
multipart/multipart.py
Commit message: ♻️ Refactor header option parser to use the standard library instead of a custom RegEx (#75)



Vulnerable code:
def parse_options_header(value):
    # 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 b';' not in value:
        return (value.lower().strip(), {})
    ctype, rest = value.split(b';', 1)

    # 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'"')

        if key == b'filename':
            if value[1:3] == b':\\' or value[:2] == b'\\\\':
                value = value.split(b'\\')[-1]

        options[key] = value


Fixed code:
from email.message import Message
from typing import Dict, Union, Tuple
def parse_options_header(value: Union[str, bytes]) -> Tuple[bytes, Dict[bytes, bytes]]:
    # Uses email.message.Message to parse the header as described in PEP 594.
    # Ref: https://peps.python.org/pep-0594/#cgi
    # 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 ";" not in value:
        return (value.lower().strip().encode('latin-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')
    for param in params:
        key, value = param
        if key == 'filename':
            if value[1:3] == ':\\' or value[:2] == '\\\\':
                value = value.split('\\')[-1]
        options[key.encode('latin-1')] = value.encode('latin-1')