CVE-2024-24762 (published 2024-02-05T15:15Z)

`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.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-1333
cwe_name: Inefficient Regular Expression Complexity
description: The product uses a regular expression with an inefficient, possibly exponential worst-case computational complexity that consumes excessive CPU cycles.
extended_description: Some regular expression engines have a feature called "backtracking". If the token cannot match, the engine "backtracks" to a position that may result in a different token that can match.
                Backtracking becomes a weakness if all of these conditions are met:
                
                    The number of possible backtracking attempts are exponential relative to the length of the input.
                    The input can fail to match the regular expression.
                    The input can be long enough.
                
		
		  Attackers can create crafted inputs that
		  intentionally cause the regular expression to use
		  excessive backtracking in a way that causes the CPU
		  consumption to spike.
		
            
            

Fix information:
repo_url: https://github.com/tiangolo/fastapi
commit_details: repo_url: https://github.com/tiangolo/fastapi
author: Sebastián Ramírez
author_date: 2024-02-03 13:32:42+01:00
msg: Merge pull request from GHSA-qf9m-vfgh-m389
repository: repo_url: https://github.com/tiangolo/fastapi
repo_name: tiangolo/fastapi
description: FastAPI framework, high performance, easy to learn, fast to code, ready for production
repo_language: Python
owner: tiangolo
file_changes: filename: pyproject.toml
change_type: ModificationType.MODIFY
diff: @@ -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",

programming_language: Python
filename: requirements-tests.txt
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: Markdown
repo_url: https://github.com/Kludex/python-multipart
commit_details: repo_url: https://github.com/Kludex/python-multipart
author: Sebastián Ramírez
author_date: 2024-02-03 12:54:23+01:00
msg: ♻️ Refactor header option parser to use the standard library instead of a custom RegEx (#75)
repository: repo_url: https://github.com/Kludex/python-multipart
repo_name: Kludex/python-multipart
description: A streaming multipart parser for Python.
repo_language: Python
owner: Kludex
file_changes: filename: multipart.py
change_type: ModificationType.MODIFY
diff: @@ -9,6 +9,8 @@ import logging
 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 @@ OPTION_RE = re.compile(OPTION_RE_STR)
 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
 
 

programming_language: Python
repo_url: https://github.com/encode/starlette
commit_details: repo_url: https://github.com/encode/starlette
author: Sebastián Ramírez
author_date: 2024-02-03 13:34:10+01:00
msg: Merge pull request from GHSA-93gm-qmq6-w238
repository: repo_url: https://github.com/encode/starlette
repo_name: encode/starlette
description: The little ASGI framework that shines. 🌟
repo_language: Python
owner: encode
file_changes: filename: pyproject.toml
change_type: ModificationType.MODIFY
diff: @@ -36,7 +36,7 @@ dependencies = [
 full = [
     "itsdangerous",
     "jinja2",
-    "python-multipart",
+    "python-multipart>=0.0.7",
     "pyyaml",
     "httpx>=0.22.0",
 ]

programming_language: Python