CVE-2023-24580
Description
An issue was discovered in the Multipart Request Parser in Django 3.2 before 3.2.18, 4.0 before 4.0.10, and 4.1 before 4.1.7. Passing certain inputs (e.g., an excessive number of parts) to multipart forms could result in too many open files or memory exhaustion, and provided a potential vector for a denial-of-service attack.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Django multipart parser before 3.2.18, 4.0.10, 4.1.7 can be exploited via excessive number of parts leading to file descriptor or memory exhaustion, enabling DoS.
Vulnerability
The multipart request parser in Django versions prior to 3.2.18, 4.0.10, and 4.1.7 does not enforce a limit on the number of uploaded file parts. An attacker can send a crafted multipart form with an excessive number of parts, causing the server to open many file descriptors or consume large amounts of memory.[1][2]
Exploitation
An attacker can exploit this by sending a specially crafted HTTP request with a multipart/form-data body containing a large number of file parts. No authentication is required if the target endpoint allows unauthenticated file uploads. The parser attempts to process each part, leading to resource exhaustion and potential denial-of-service.[2]
Impact
Successful exploitation results in a denial-of-service condition due to too many open files or memory exhaustion, potentially crashing the server or rendering it unresponsive.[1]
Mitigation
Django has released patched versions (3.2.18, 4.0.10, 4.1.7) that introduce a new setting DATA_UPLOAD_MAX_NUMBER_FIELDS and a limit on the number of files. The commits add a counter for files and raise a TooManyFilesSent error when the limit is exceeded.[3][4] Users should upgrade to the latest versions or manually apply the patches.
- NVD - CVE-2023-24580
- oss-security - Django - CVE-2023-24580: Potential denial-of-service vulnerability in file uploads
- [3.2.x] Fixed CVE-2023-24580 -- Prevented DoS with too many uploaded … · django/django@a665ed5
- [4.1.x] Fixed CVE-2023-24580 -- Prevented DoS with too many uploaded … · django/django@628b33a
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | >= 3.2a1, < 3.2.18 | 3.2.18 |
DjangoPyPI | >= 4.1a1, < 4.1.7 | 4.1.7 |
DjangoPyPI | >= 4.0a1, < 4.0.10 | 4.0.10 |
Affected products
18- Django/Djangodescription
- osv-coords17 versionspkg:bitnami/djangopkg:pypi/djangopkg:rpm/opensuse/python-Django1&distro=openSUSE%20Leap%2015.4pkg:rpm/opensuse/python-Django4&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/python-Django6&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/python-Django&distro=openSUSE%20Leap%2015.4pkg:rpm/opensuse/python-Django&distro=openSUSE%20Leap%2015.5pkg:rpm/opensuse/python-Django&distro=openSUSE%20Tumbleweedpkg:rpm/suse/python-Django1&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/python-Django1&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/python-Django1&distro=SUSE%20Package%20Hub%2015%20SP4pkg:rpm/suse/python-Django&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/python-Django&distro=SUSE%20Package%20Hub%2012%20SP1pkg:rpm/suse/python-Django&distro=SUSE%20Package%20Hub%2015%20SP4pkg:rpm/suse/python-Django&distro=SUSE%20Package%20Hub%2015%20SP5
>= 3.2.0, < 3.2.18+ 16 more
- (no CPE)range: >= 3.2.0, < 3.2.18
- (no CPE)range: >= 3.2a1, < 3.2.18
- (no CPE)range: < 1.11.29-bp154.2.3.1
- (no CPE)range: < 4.2.14-1.1
- (no CPE)range: < 6.0-1.1
- (no CPE)range: < 2.2.28-bp154.2.9.1
- (no CPE)range: < 2.2.28-bp155.7.3.1
- (no CPE)range: < 4.1.7-1.1
- (no CPE)range: < 1.11.29-3.44.1
- (no CPE)range: < 1.11.29-3.44.1
- (no CPE)range: < 1.11.29-bp154.2.3.1
- (no CPE)range: < 1.11.29-3.45.1
- (no CPE)range: < 1.11.29-3.45.1
- (no CPE)range: < 1.11.29-3.45.1
- (no CPE)range: < 1.11.15-2.1
- (no CPE)range: < 2.2.28-bp154.2.9.1
- (no CPE)range: < 2.2.28-bp155.7.3.1
Patches
3628b33a854a9[4.1.x] Fixed CVE-2023-24580 -- Prevented DoS with too many uploaded files.
12 files changed · +213 −23
django/conf/global_settings.py+4 −0 modified@@ -313,6 +313,10 @@ def gettext_noop(s): # SuspiciousOperation (TooManyFieldsSent) is raised. DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000 +# Maximum number of files encoded in a multipart upload that will be read +# before a SuspiciousOperation (TooManyFilesSent) is raised. +DATA_UPLOAD_MAX_NUMBER_FILES = 100 + # Directory in which upload streamed files will be temporarily saved. A value of # `None` will make Django use the operating system's default temporary directory # (i.e. "/tmp" on *nix systems).
django/core/exceptions.py+9 −0 modified@@ -67,6 +67,15 @@ class TooManyFieldsSent(SuspiciousOperation): pass +class TooManyFilesSent(SuspiciousOperation): + """ + The number of fields in a GET or POST request exceeded + settings.DATA_UPLOAD_MAX_NUMBER_FILES. + """ + + pass + + class RequestDataTooBig(SuspiciousOperation): """ The size of the request (excluding any file uploads) exceeded
django/core/handlers/exception.py+2 −1 modified@@ -13,6 +13,7 @@ RequestDataTooBig, SuspiciousOperation, TooManyFieldsSent, + TooManyFilesSent, ) from django.http import Http404 from django.http.multipartparser import MultiPartParserError @@ -111,7 +112,7 @@ def response_for_exception(request, exc): exception=exc, ) elif isinstance(exc, SuspiciousOperation): - if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent)): + if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent, TooManyFilesSent)): # POST data can't be accessed again, otherwise the original # exception would be raised. request._mark_post_parse_error()
django/http/multipartparser.py+51 −13 modified@@ -15,6 +15,7 @@ RequestDataTooBig, SuspiciousMultipartForm, TooManyFieldsSent, + TooManyFilesSent, ) from django.core.files.uploadhandler import SkipFile, StopFutureHandlers, StopUpload from django.utils.datastructures import MultiValueDict @@ -39,6 +40,7 @@ class InputStreamExhausted(Exception): RAW = "raw" FILE = "file" FIELD = "field" +FIELD_TYPES = frozenset([FIELD, RAW]) class MultiPartParser: @@ -111,6 +113,22 @@ def __init__(self, META, input_data, upload_handlers, encoding=None): self._upload_handlers = upload_handlers def parse(self): + # Call the actual parse routine and close all open files in case of + # errors. This is needed because if exceptions are thrown the + # MultiPartParser will not be garbage collected immediately and + # resources would be kept alive. This is only needed for errors because + # the Request object closes all uploaded files at the end of the + # request. + try: + return self._parse() + except Exception: + if hasattr(self, "_files"): + for _, files in self._files.lists(): + for fileobj in files: + fileobj.close() + raise + + def _parse(self): """ Parse the POST data and break it into a FILES MultiValueDict and a POST MultiValueDict. @@ -156,6 +174,8 @@ def parse(self): num_bytes_read = 0 # To count the number of keys in the request. num_post_keys = 0 + # To count the number of files in the request. + num_files = 0 # To limit the amount of data read from the request. read_size = None # Whether a file upload is finished. @@ -171,6 +191,20 @@ def parse(self): old_field_name = None uploaded_file = True + if ( + item_type in FIELD_TYPES + and settings.DATA_UPLOAD_MAX_NUMBER_FIELDS is not None + ): + # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS. + num_post_keys += 1 + # 2 accounts for empty raw fields before and after the + # last boundary. + if settings.DATA_UPLOAD_MAX_NUMBER_FIELDS + 2 < num_post_keys: + raise TooManyFieldsSent( + "The number of GET/POST parameters exceeded " + "settings.DATA_UPLOAD_MAX_NUMBER_FIELDS." + ) + try: disposition = meta_data["content-disposition"][1] field_name = disposition["name"].strip() @@ -183,17 +217,6 @@ def parse(self): field_name = force_str(field_name, encoding, errors="replace") if item_type == FIELD: - # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS. - num_post_keys += 1 - if ( - settings.DATA_UPLOAD_MAX_NUMBER_FIELDS is not None - and settings.DATA_UPLOAD_MAX_NUMBER_FIELDS < num_post_keys - ): - raise TooManyFieldsSent( - "The number of GET/POST parameters exceeded " - "settings.DATA_UPLOAD_MAX_NUMBER_FIELDS." - ) - # Avoid reading more than DATA_UPLOAD_MAX_MEMORY_SIZE. if settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None: read_size = ( @@ -228,6 +251,16 @@ def parse(self): field_name, force_str(data, encoding, errors="replace") ) elif item_type == FILE: + # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FILES. + num_files += 1 + if ( + settings.DATA_UPLOAD_MAX_NUMBER_FILES is not None + and num_files > settings.DATA_UPLOAD_MAX_NUMBER_FILES + ): + raise TooManyFilesSent( + "The number of files exceeded " + "settings.DATA_UPLOAD_MAX_NUMBER_FILES." + ) # This is a file, use the handler... file_name = disposition.get("filename") if file_name: @@ -305,8 +338,13 @@ def parse(self): # Handle file upload completions on next iteration. old_field_name = field_name else: - # If this is neither a FIELD or a FILE, just exhaust the stream. - exhaust(stream) + # If this is neither a FIELD nor a FILE, exhaust the field + # stream. Note: There could be an error here at some point, + # but there will be at least two RAW types (before and + # after the other boundaries). This branch is usually not + # reached at all, because a missing content-disposition + # header will skip the whole boundary. + exhaust(field_stream) except StopUpload as e: self._close_files() if not e.connection_reset:
django/http/request.py+6 −2 modified@@ -13,7 +13,11 @@ TooManyFieldsSent, ) from django.core.files import uploadhandler -from django.http.multipartparser import MultiPartParser, MultiPartParserError +from django.http.multipartparser import ( + MultiPartParser, + MultiPartParserError, + TooManyFilesSent, +) from django.utils.datastructures import ( CaseInsensitiveMapping, ImmutableList, @@ -367,7 +371,7 @@ def _load_post_and_files(self): data = self try: self._post, self._files = self.parse_file_upload(self.META, data) - except MultiPartParserError: + except (MultiPartParserError, TooManyFilesSent): # An error occurred while parsing POST data. Since when # formatting the error the request handler might access # self.POST, set self._post and self._file to prevent
docs/ref/exceptions.txt+5 −0 modified@@ -84,12 +84,17 @@ Django core exception classes are defined in ``django.core.exceptions``. * ``SuspiciousMultipartForm`` * ``SuspiciousSession`` * ``TooManyFieldsSent`` + * ``TooManyFilesSent`` If a ``SuspiciousOperation`` exception reaches the ASGI/WSGI handler level it is logged at the ``Error`` level and results in a :class:`~django.http.HttpResponseBadRequest`. See the :doc:`logging documentation </topics/logging/>` for more information. +.. versionchanged:: 3.2.18 + + ``SuspiciousOperation`` is raised when too many files are submitted. + ``PermissionDenied`` --------------------
docs/ref/settings.txt+23 −0 modified@@ -1108,6 +1108,28 @@ could be used as a denial-of-service attack vector if left unchecked. Since web servers don't typically perform deep request inspection, it's not possible to perform a similar check at that level. +.. setting:: DATA_UPLOAD_MAX_NUMBER_FILES + +``DATA_UPLOAD_MAX_NUMBER_FILES`` +-------------------------------- + +.. versionadded:: 3.2.18 + +Default: ``100`` + +The maximum number of files that may be received via POST in a +``multipart/form-data`` encoded request before a +:exc:`~django.core.exceptions.SuspiciousOperation` (``TooManyFiles``) is +raised. You can set this to ``None`` to disable the check. Applications that +are expected to receive an unusually large number of file fields should tune +this setting. + +The number of accepted files is correlated to the amount of time and memory +needed to process the request. Large requests could be used as a +denial-of-service attack vector if left unchecked. Since web servers don't +typically perform deep request inspection, it's not possible to perform a +similar check at that level. + .. setting:: DATABASE_ROUTERS ``DATABASE_ROUTERS`` @@ -3727,6 +3749,7 @@ HTTP ---- * :setting:`DATA_UPLOAD_MAX_MEMORY_SIZE` * :setting:`DATA_UPLOAD_MAX_NUMBER_FIELDS` +* :setting:`DATA_UPLOAD_MAX_NUMBER_FILES` * :setting:`DEFAULT_CHARSET` * :setting:`DISALLOWED_USER_AGENTS` * :setting:`FORCE_SCRIPT_NAME`
docs/releases/3.2.18.txt+9 −1 modified@@ -6,4 +6,12 @@ Django 3.2.18 release notes Django 3.2.18 fixes a security issue with severity "moderate" in 3.2.17. -... +CVE-2023-24580: Potential denial-of-service vulnerability in file uploads +========================================================================= + +Passing certain inputs to multipart forms could result in too many open files +or memory exhaustion, and provided a potential vector for a denial-of-service +attack. + +The number of files parts parsed is now limited via the new +:setting:`DATA_UPLOAD_MAX_NUMBER_FILES` setting.
docs/releases/4.0.10.txt+9 −1 modified@@ -6,4 +6,12 @@ Django 4.0.10 release notes Django 4.0.10 fixes a security issue with severity "moderate" in 4.0.9. -... +CVE-2023-24580: Potential denial-of-service vulnerability in file uploads +========================================================================= + +Passing certain inputs to multipart forms could result in too many open files +or memory exhaustion, and provided a potential vector for a denial-of-service +attack. + +The number of files parts parsed is now limited via the new +:setting:`DATA_UPLOAD_MAX_NUMBER_FILES` setting.
docs/releases/4.1.7.txt+11 −3 modified@@ -4,10 +4,18 @@ Django 4.1.7 release notes *February 14, 2023* -Django 4.1.7 fixes a security issue with severity "moderate" and several bugs -in 4.1.6. +Django 4.1.7 fixes a security issue with severity "moderate" and a bug in +4.1.6. -... +CVE-2023-24580: Potential denial-of-service vulnerability in file uploads +========================================================================= + +Passing certain inputs to multipart forms could result in too many open files +or memory exhaustion, and provided a potential vector for a denial-of-service +attack. + +The number of files parts parsed is now limited via the new +:setting:`DATA_UPLOAD_MAX_NUMBER_FILES` setting. Bugfixes ========
tests/handlers/test_exception.py+30 −1 modified@@ -1,6 +1,11 @@ from django.core.handlers.wsgi import WSGIHandler from django.test import SimpleTestCase, override_settings -from django.test.client import FakePayload +from django.test.client import ( + BOUNDARY, + MULTIPART_CONTENT, + FakePayload, + encode_multipart, +) class ExceptionHandlerTests(SimpleTestCase): @@ -24,3 +29,27 @@ def test_data_upload_max_memory_size_exceeded(self): def test_data_upload_max_number_fields_exceeded(self): response = WSGIHandler()(self.get_suspicious_environ(), lambda *a, **k: None) self.assertEqual(response.status_code, 400) + + @override_settings(DATA_UPLOAD_MAX_NUMBER_FILES=2) + def test_data_upload_max_number_files_exceeded(self): + payload = FakePayload( + encode_multipart( + BOUNDARY, + { + "a.txt": "Hello World!", + "b.txt": "Hello Django!", + "c.txt": "Hello Python!", + }, + ) + ) + environ = { + "REQUEST_METHOD": "POST", + "CONTENT_TYPE": MULTIPART_CONTENT, + "CONTENT_LENGTH": len(payload), + "wsgi.input": payload, + "SERVER_NAME": "test", + "SERVER_PORT": "8000", + } + + response = WSGIHandler()(environ, lambda *a, **k: None) + self.assertEqual(response.status_code, 400)
tests/requests/test_data_upload_settings.py+54 −1 modified@@ -1,13 +1,20 @@ from io import BytesIO -from django.core.exceptions import RequestDataTooBig, TooManyFieldsSent +from django.core.exceptions import ( + RequestDataTooBig, + TooManyFieldsSent, + TooManyFilesSent, +) from django.core.handlers.wsgi import WSGIRequest from django.test import SimpleTestCase from django.test.client import FakePayload TOO_MANY_FIELDS_MSG = ( "The number of GET/POST parameters exceeded settings.DATA_UPLOAD_MAX_NUMBER_FIELDS." ) +TOO_MANY_FILES_MSG = ( + "The number of files exceeded settings.DATA_UPLOAD_MAX_NUMBER_FILES." +) TOO_MUCH_DATA_MSG = "Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE." @@ -191,6 +198,52 @@ def test_no_limit(self): self.request._load_post_and_files() +class DataUploadMaxNumberOfFilesMultipartPost(SimpleTestCase): + def setUp(self): + payload = FakePayload( + "\r\n".join( + [ + "--boundary", + ( + 'Content-Disposition: form-data; name="name1"; ' + 'filename="name1.txt"' + ), + "", + "value1", + "--boundary", + ( + 'Content-Disposition: form-data; name="name2"; ' + 'filename="name2.txt"' + ), + "", + "value2", + "--boundary--", + ] + ) + ) + self.request = WSGIRequest( + { + "REQUEST_METHOD": "POST", + "CONTENT_TYPE": "multipart/form-data; boundary=boundary", + "CONTENT_LENGTH": len(payload), + "wsgi.input": payload, + } + ) + + def test_number_exceeded(self): + with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=1): + with self.assertRaisesMessage(TooManyFilesSent, TOO_MANY_FILES_MSG): + self.request._load_post_and_files() + + def test_number_not_exceeded(self): + with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=2): + self.request._load_post_and_files() + + def test_no_limit(self): + with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=None): + self.request._load_post_and_files() + + class DataUploadMaxNumberOfFieldsFormPost(SimpleTestCase): def setUp(self): payload = FakePayload("\r\n".join(["a=1&a=2&a=3", ""]))
83f1ea83e455[4.0.x] Fixed CVE-2023-24580 -- Prevented DoS with too many uploaded files.
11 files changed · +202 −20
django/conf/global_settings.py+4 −0 modified@@ -309,6 +309,10 @@ def gettext_noop(s): # SuspiciousOperation (TooManyFieldsSent) is raised. DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000 +# Maximum number of files encoded in a multipart upload that will be read +# before a SuspiciousOperation (TooManyFilesSent) is raised. +DATA_UPLOAD_MAX_NUMBER_FILES = 100 + # Directory in which upload streamed files will be temporarily saved. A value of # `None` will make Django use the operating system's default temporary directory # (i.e. "/tmp" on *nix systems).
django/core/exceptions.py+9 −0 modified@@ -67,6 +67,15 @@ class TooManyFieldsSent(SuspiciousOperation): pass +class TooManyFilesSent(SuspiciousOperation): + """ + The number of fields in a GET or POST request exceeded + settings.DATA_UPLOAD_MAX_NUMBER_FILES. + """ + + pass + + class RequestDataTooBig(SuspiciousOperation): """ The size of the request (excluding any file uploads) exceeded
django/core/handlers/exception.py+2 −1 modified@@ -13,6 +13,7 @@ RequestDataTooBig, SuspiciousOperation, TooManyFieldsSent, + TooManyFilesSent, ) from django.http import Http404 from django.http.multipartparser import MultiPartParserError @@ -111,7 +112,7 @@ def response_for_exception(request, exc): exc_info=sys.exc_info(), ) elif isinstance(exc, SuspiciousOperation): - if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent)): + if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent, TooManyFilesSent)): # POST data can't be accessed again, otherwise the original # exception would be raised. request._mark_post_parse_error()
django/http/multipartparser.py+51 −13 modified@@ -16,6 +16,7 @@ RequestDataTooBig, SuspiciousMultipartForm, TooManyFieldsSent, + TooManyFilesSent, ) from django.core.files.uploadhandler import SkipFile, StopFutureHandlers, StopUpload from django.utils.datastructures import MultiValueDict @@ -39,6 +40,7 @@ class InputStreamExhausted(Exception): RAW = "raw" FILE = "file" FIELD = "field" +FIELD_TYPES = frozenset([FIELD, RAW]) class MultiPartParser: @@ -109,6 +111,22 @@ def __init__(self, META, input_data, upload_handlers, encoding=None): self._upload_handlers = upload_handlers def parse(self): + # Call the actual parse routine and close all open files in case of + # errors. This is needed because if exceptions are thrown the + # MultiPartParser will not be garbage collected immediately and + # resources would be kept alive. This is only needed for errors because + # the Request object closes all uploaded files at the end of the + # request. + try: + return self._parse() + except Exception: + if hasattr(self, "_files"): + for _, files in self._files.lists(): + for fileobj in files: + fileobj.close() + raise + + def _parse(self): """ Parse the POST data and break it into a FILES MultiValueDict and a POST MultiValueDict. @@ -154,6 +172,8 @@ def parse(self): num_bytes_read = 0 # To count the number of keys in the request. num_post_keys = 0 + # To count the number of files in the request. + num_files = 0 # To limit the amount of data read from the request. read_size = None # Whether a file upload is finished. @@ -169,6 +189,20 @@ def parse(self): old_field_name = None uploaded_file = True + if ( + item_type in FIELD_TYPES + and settings.DATA_UPLOAD_MAX_NUMBER_FIELDS is not None + ): + # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS. + num_post_keys += 1 + # 2 accounts for empty raw fields before and after the + # last boundary. + if settings.DATA_UPLOAD_MAX_NUMBER_FIELDS + 2 < num_post_keys: + raise TooManyFieldsSent( + "The number of GET/POST parameters exceeded " + "settings.DATA_UPLOAD_MAX_NUMBER_FIELDS." + ) + try: disposition = meta_data["content-disposition"][1] field_name = disposition["name"].strip() @@ -181,17 +215,6 @@ def parse(self): field_name = force_str(field_name, encoding, errors="replace") if item_type == FIELD: - # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS. - num_post_keys += 1 - if ( - settings.DATA_UPLOAD_MAX_NUMBER_FIELDS is not None - and settings.DATA_UPLOAD_MAX_NUMBER_FIELDS < num_post_keys - ): - raise TooManyFieldsSent( - "The number of GET/POST parameters exceeded " - "settings.DATA_UPLOAD_MAX_NUMBER_FIELDS." - ) - # Avoid reading more than DATA_UPLOAD_MAX_MEMORY_SIZE. if settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None: read_size = ( @@ -226,6 +249,16 @@ def parse(self): field_name, force_str(data, encoding, errors="replace") ) elif item_type == FILE: + # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FILES. + num_files += 1 + if ( + settings.DATA_UPLOAD_MAX_NUMBER_FILES is not None + and num_files > settings.DATA_UPLOAD_MAX_NUMBER_FILES + ): + raise TooManyFilesSent( + "The number of files exceeded " + "settings.DATA_UPLOAD_MAX_NUMBER_FILES." + ) # This is a file, use the handler... file_name = disposition.get("filename") if file_name: @@ -303,8 +336,13 @@ def parse(self): # Handle file upload completions on next iteration. old_field_name = field_name else: - # If this is neither a FIELD or a FILE, just exhaust the stream. - exhaust(stream) + # If this is neither a FIELD nor a FILE, exhaust the field + # stream. Note: There could be an error here at some point, + # but there will be at least two RAW types (before and + # after the other boundaries). This branch is usually not + # reached at all, because a missing content-disposition + # header will skip the whole boundary. + exhaust(field_stream) except StopUpload as e: self._close_files() if not e.connection_reset:
django/http/request.py+6 −2 modified@@ -14,7 +14,11 @@ TooManyFieldsSent, ) from django.core.files import uploadhandler -from django.http.multipartparser import MultiPartParser, MultiPartParserError +from django.http.multipartparser import ( + MultiPartParser, + MultiPartParserError, + TooManyFilesSent, +) from django.utils.datastructures import ( CaseInsensitiveMapping, ImmutableList, @@ -367,7 +371,7 @@ def _load_post_and_files(self): data = self try: self._post, self._files = self.parse_file_upload(self.META, data) - except MultiPartParserError: + except (MultiPartParserError, TooManyFilesSent): # An error occurred while parsing POST data. Since when # formatting the error the request handler might access # self.POST, set self._post and self._file to prevent
docs/ref/exceptions.txt+5 −0 modified@@ -84,12 +84,17 @@ Django core exception classes are defined in ``django.core.exceptions``. * ``SuspiciousMultipartForm`` * ``SuspiciousSession`` * ``TooManyFieldsSent`` + * ``TooManyFilesSent`` If a ``SuspiciousOperation`` exception reaches the ASGI/WSGI handler level it is logged at the ``Error`` level and results in a :class:`~django.http.HttpResponseBadRequest`. See the :doc:`logging documentation </topics/logging/>` for more information. +.. versionchanged:: 3.2.18 + + ``SuspiciousOperation`` is raised when too many files are submitted. + ``PermissionDenied`` --------------------
docs/ref/settings.txt+23 −0 modified@@ -1076,6 +1076,28 @@ could be used as a denial-of-service attack vector if left unchecked. Since web servers don't typically perform deep request inspection, it's not possible to perform a similar check at that level. +.. setting:: DATA_UPLOAD_MAX_NUMBER_FILES + +``DATA_UPLOAD_MAX_NUMBER_FILES`` +-------------------------------- + +.. versionadded:: 3.2.18 + +Default: ``100`` + +The maximum number of files that may be received via POST in a +``multipart/form-data`` encoded request before a +:exc:`~django.core.exceptions.SuspiciousOperation` (``TooManyFiles``) is +raised. You can set this to ``None`` to disable the check. Applications that +are expected to receive an unusually large number of file fields should tune +this setting. + +The number of accepted files is correlated to the amount of time and memory +needed to process the request. Large requests could be used as a +denial-of-service attack vector if left unchecked. Since web servers don't +typically perform deep request inspection, it's not possible to perform a +similar check at that level. + .. setting:: DATABASE_ROUTERS ``DATABASE_ROUTERS`` @@ -3658,6 +3680,7 @@ HTTP ---- * :setting:`DATA_UPLOAD_MAX_MEMORY_SIZE` * :setting:`DATA_UPLOAD_MAX_NUMBER_FIELDS` +* :setting:`DATA_UPLOAD_MAX_NUMBER_FILES` * :setting:`DEFAULT_CHARSET` * :setting:`DISALLOWED_USER_AGENTS` * :setting:`FORCE_SCRIPT_NAME`
docs/releases/3.2.18.txt+9 −1 modified@@ -6,4 +6,12 @@ Django 3.2.18 release notes Django 3.2.18 fixes a security issue with severity "moderate" in 3.2.17. -... +CVE-2023-24580: Potential denial-of-service vulnerability in file uploads +========================================================================= + +Passing certain inputs to multipart forms could result in too many open files +or memory exhaustion, and provided a potential vector for a denial-of-service +attack. + +The number of files parts parsed is now limited via the new +:setting:`DATA_UPLOAD_MAX_NUMBER_FILES` setting.
docs/releases/4.0.10.txt+9 −1 modified@@ -6,4 +6,12 @@ Django 4.0.10 release notes Django 4.0.10 fixes a security issue with severity "moderate" in 4.0.9. -... +CVE-2023-24580: Potential denial-of-service vulnerability in file uploads +========================================================================= + +Passing certain inputs to multipart forms could result in too many open files +or memory exhaustion, and provided a potential vector for a denial-of-service +attack. + +The number of files parts parsed is now limited via the new +:setting:`DATA_UPLOAD_MAX_NUMBER_FILES` setting.
tests/handlers/test_exception.py+30 −1 modified@@ -1,6 +1,11 @@ from django.core.handlers.wsgi import WSGIHandler from django.test import SimpleTestCase, override_settings -from django.test.client import FakePayload +from django.test.client import ( + BOUNDARY, + MULTIPART_CONTENT, + FakePayload, + encode_multipart, +) class ExceptionHandlerTests(SimpleTestCase): @@ -24,3 +29,27 @@ def test_data_upload_max_memory_size_exceeded(self): def test_data_upload_max_number_fields_exceeded(self): response = WSGIHandler()(self.get_suspicious_environ(), lambda *a, **k: None) self.assertEqual(response.status_code, 400) + + @override_settings(DATA_UPLOAD_MAX_NUMBER_FILES=2) + def test_data_upload_max_number_files_exceeded(self): + payload = FakePayload( + encode_multipart( + BOUNDARY, + { + "a.txt": "Hello World!", + "b.txt": "Hello Django!", + "c.txt": "Hello Python!", + }, + ) + ) + environ = { + "REQUEST_METHOD": "POST", + "CONTENT_TYPE": MULTIPART_CONTENT, + "CONTENT_LENGTH": len(payload), + "wsgi.input": payload, + "SERVER_NAME": "test", + "SERVER_PORT": "8000", + } + + response = WSGIHandler()(environ, lambda *a, **k: None) + self.assertEqual(response.status_code, 400)
tests/requests/test_data_upload_settings.py+54 −1 modified@@ -1,13 +1,20 @@ from io import BytesIO -from django.core.exceptions import RequestDataTooBig, TooManyFieldsSent +from django.core.exceptions import ( + RequestDataTooBig, + TooManyFieldsSent, + TooManyFilesSent, +) from django.core.handlers.wsgi import WSGIRequest from django.test import SimpleTestCase from django.test.client import FakePayload TOO_MANY_FIELDS_MSG = ( "The number of GET/POST parameters exceeded settings.DATA_UPLOAD_MAX_NUMBER_FIELDS." ) +TOO_MANY_FILES_MSG = ( + "The number of files exceeded settings.DATA_UPLOAD_MAX_NUMBER_FILES." +) TOO_MUCH_DATA_MSG = "Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE." @@ -191,6 +198,52 @@ def test_no_limit(self): self.request._load_post_and_files() +class DataUploadMaxNumberOfFilesMultipartPost(SimpleTestCase): + def setUp(self): + payload = FakePayload( + "\r\n".join( + [ + "--boundary", + ( + 'Content-Disposition: form-data; name="name1"; ' + 'filename="name1.txt"' + ), + "", + "value1", + "--boundary", + ( + 'Content-Disposition: form-data; name="name2"; ' + 'filename="name2.txt"' + ), + "", + "value2", + "--boundary--", + ] + ) + ) + self.request = WSGIRequest( + { + "REQUEST_METHOD": "POST", + "CONTENT_TYPE": "multipart/form-data; boundary=boundary", + "CONTENT_LENGTH": len(payload), + "wsgi.input": payload, + } + ) + + def test_number_exceeded(self): + with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=1): + with self.assertRaisesMessage(TooManyFilesSent, TOO_MANY_FILES_MSG): + self.request._load_post_and_files() + + def test_number_not_exceeded(self): + with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=2): + self.request._load_post_and_files() + + def test_no_limit(self): + with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=None): + self.request._load_post_and_files() + + class DataUploadMaxNumberOfFieldsFormPost(SimpleTestCase): def setUp(self): payload = FakePayload("\r\n".join(["a=1&a=2&a=3", ""]))
a665ed5179f5[3.2.x] Fixed CVE-2023-24580 -- Prevented DoS with too many uploaded files.
10 files changed · +184 −18
django/conf/global_settings.py+4 −0 modified@@ -303,6 +303,10 @@ def gettext_noop(s): # SuspiciousOperation (TooManyFieldsSent) is raised. DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000 +# Maximum number of files encoded in a multipart upload that will be read +# before a SuspiciousOperation (TooManyFilesSent) is raised. +DATA_UPLOAD_MAX_NUMBER_FILES = 100 + # Directory in which upload streamed files will be temporarily saved. A value of # `None` will make Django use the operating system's default temporary directory # (i.e. "/tmp" on *nix systems).
django/core/exceptions.py+9 −0 modified@@ -58,6 +58,15 @@ class TooManyFieldsSent(SuspiciousOperation): pass +class TooManyFilesSent(SuspiciousOperation): + """ + The number of fields in a GET or POST request exceeded + settings.DATA_UPLOAD_MAX_NUMBER_FILES. + """ + + pass + + class RequestDataTooBig(SuspiciousOperation): """ The size of the request (excluding any file uploads) exceeded
django/core/handlers/exception.py+2 −2 modified@@ -9,7 +9,7 @@ from django.core import signals from django.core.exceptions import ( BadRequest, PermissionDenied, RequestDataTooBig, SuspiciousOperation, - TooManyFieldsSent, + TooManyFieldsSent, TooManyFilesSent, ) from django.http import Http404 from django.http.multipartparser import MultiPartParserError @@ -88,7 +88,7 @@ def response_for_exception(request, exc): exc_info=sys.exc_info(), ) elif isinstance(exc, SuspiciousOperation): - if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent)): + if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent, TooManyFilesSent)): # POST data can't be accessed again, otherwise the original # exception would be raised. request._mark_post_parse_error()
django/http/multipartparser.py+51 −11 modified@@ -14,6 +14,7 @@ from django.conf import settings from django.core.exceptions import ( RequestDataTooBig, SuspiciousMultipartForm, TooManyFieldsSent, + TooManyFilesSent, ) from django.core.files.uploadhandler import ( SkipFile, StopFutureHandlers, StopUpload, @@ -38,6 +39,7 @@ class InputStreamExhausted(Exception): RAW = "raw" FILE = "file" FIELD = "field" +FIELD_TYPES = frozenset([FIELD, RAW]) class MultiPartParser: @@ -102,6 +104,22 @@ def __init__(self, META, input_data, upload_handlers, encoding=None): self._upload_handlers = upload_handlers def parse(self): + # Call the actual parse routine and close all open files in case of + # errors. This is needed because if exceptions are thrown the + # MultiPartParser will not be garbage collected immediately and + # resources would be kept alive. This is only needed for errors because + # the Request object closes all uploaded files at the end of the + # request. + try: + return self._parse() + except Exception: + if hasattr(self, "_files"): + for _, files in self._files.lists(): + for fileobj in files: + fileobj.close() + raise + + def _parse(self): """ Parse the POST data and break it into a FILES MultiValueDict and a POST MultiValueDict. @@ -147,6 +165,8 @@ def parse(self): num_bytes_read = 0 # To count the number of keys in the request. num_post_keys = 0 + # To count the number of files in the request. + num_files = 0 # To limit the amount of data read from the request. read_size = None # Whether a file upload is finished. @@ -162,6 +182,20 @@ def parse(self): old_field_name = None uploaded_file = True + if ( + item_type in FIELD_TYPES and + settings.DATA_UPLOAD_MAX_NUMBER_FIELDS is not None + ): + # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS. + num_post_keys += 1 + # 2 accounts for empty raw fields before and after the + # last boundary. + if settings.DATA_UPLOAD_MAX_NUMBER_FIELDS + 2 < num_post_keys: + raise TooManyFieldsSent( + "The number of GET/POST parameters exceeded " + "settings.DATA_UPLOAD_MAX_NUMBER_FIELDS." + ) + try: disposition = meta_data['content-disposition'][1] field_name = disposition['name'].strip() @@ -174,15 +208,6 @@ def parse(self): field_name = force_str(field_name, encoding, errors='replace') if item_type == FIELD: - # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS. - num_post_keys += 1 - if (settings.DATA_UPLOAD_MAX_NUMBER_FIELDS is not None and - settings.DATA_UPLOAD_MAX_NUMBER_FIELDS < num_post_keys): - raise TooManyFieldsSent( - 'The number of GET/POST parameters exceeded ' - 'settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.' - ) - # Avoid reading more than DATA_UPLOAD_MAX_MEMORY_SIZE. if settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None: read_size = settings.DATA_UPLOAD_MAX_MEMORY_SIZE - num_bytes_read @@ -208,6 +233,16 @@ def parse(self): self._post.appendlist(field_name, force_str(data, encoding, errors='replace')) elif item_type == FILE: + # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FILES. + num_files += 1 + if ( + settings.DATA_UPLOAD_MAX_NUMBER_FILES is not None and + num_files > settings.DATA_UPLOAD_MAX_NUMBER_FILES + ): + raise TooManyFilesSent( + "The number of files exceeded " + "settings.DATA_UPLOAD_MAX_NUMBER_FILES." + ) # This is a file, use the handler... file_name = disposition.get('filename') if file_name: @@ -276,8 +311,13 @@ def parse(self): # Handle file upload completions on next iteration. old_field_name = field_name else: - # If this is neither a FIELD or a FILE, just exhaust the stream. - exhaust(stream) + # If this is neither a FIELD nor a FILE, exhaust the field + # stream. Note: There could be an error here at some point, + # but there will be at least two RAW types (before and + # after the other boundaries). This branch is usually not + # reached at all, because a missing content-disposition + # header will skip the whole boundary. + exhaust(field_stream) except StopUpload as e: self._close_files() if not e.connection_reset:
django/http/request.py+4 −2 modified@@ -12,7 +12,9 @@ DisallowedHost, ImproperlyConfigured, RequestDataTooBig, TooManyFieldsSent, ) from django.core.files import uploadhandler -from django.http.multipartparser import MultiPartParser, MultiPartParserError +from django.http.multipartparser import ( + MultiPartParser, MultiPartParserError, TooManyFilesSent, +) from django.utils.datastructures import ( CaseInsensitiveMapping, ImmutableList, MultiValueDict, ) @@ -360,7 +362,7 @@ def _load_post_and_files(self): data = self try: self._post, self._files = self.parse_file_upload(self.META, data) - except MultiPartParserError: + except (MultiPartParserError, TooManyFilesSent): # An error occurred while parsing POST data. Since when # formatting the error the request handler might access # self.POST, set self._post and self._file to prevent
docs/ref/exceptions.txt+5 −0 modified@@ -84,12 +84,17 @@ Django core exception classes are defined in ``django.core.exceptions``. * ``SuspiciousMultipartForm`` * ``SuspiciousSession`` * ``TooManyFieldsSent`` + * ``TooManyFilesSent`` If a ``SuspiciousOperation`` exception reaches the ASGI/WSGI handler level it is logged at the ``Error`` level and results in a :class:`~django.http.HttpResponseBadRequest`. See the :doc:`logging documentation </topics/logging/>` for more information. +.. versionchanged:: 3.2.18 + + ``SuspiciousOperation`` is raised when too many files are submitted. + ``PermissionDenied`` --------------------
docs/ref/settings.txt+23 −0 modified@@ -1063,6 +1063,28 @@ could be used as a denial-of-service attack vector if left unchecked. Since web servers don't typically perform deep request inspection, it's not possible to perform a similar check at that level. +.. setting:: DATA_UPLOAD_MAX_NUMBER_FILES + +``DATA_UPLOAD_MAX_NUMBER_FILES`` +-------------------------------- + +.. versionadded:: 3.2.18 + +Default: ``100`` + +The maximum number of files that may be received via POST in a +``multipart/form-data`` encoded request before a +:exc:`~django.core.exceptions.SuspiciousOperation` (``TooManyFiles``) is +raised. You can set this to ``None`` to disable the check. Applications that +are expected to receive an unusually large number of file fields should tune +this setting. + +The number of accepted files is correlated to the amount of time and memory +needed to process the request. Large requests could be used as a +denial-of-service attack vector if left unchecked. Since web servers don't +typically perform deep request inspection, it's not possible to perform a +similar check at that level. + .. setting:: DATABASE_ROUTERS ``DATABASE_ROUTERS`` @@ -3671,6 +3693,7 @@ HTTP ---- * :setting:`DATA_UPLOAD_MAX_MEMORY_SIZE` * :setting:`DATA_UPLOAD_MAX_NUMBER_FIELDS` +* :setting:`DATA_UPLOAD_MAX_NUMBER_FILES` * :setting:`DEFAULT_CHARSET` * :setting:`DISALLOWED_USER_AGENTS` * :setting:`FORCE_SCRIPT_NAME`
docs/releases/3.2.18.txt+9 −1 modified@@ -6,4 +6,12 @@ Django 3.2.18 release notes Django 3.2.18 fixes a security issue with severity "moderate" in 3.2.17. -... +CVE-2023-24580: Potential denial-of-service vulnerability in file uploads +========================================================================= + +Passing certain inputs to multipart forms could result in too many open files +or memory exhaustion, and provided a potential vector for a denial-of-service +attack. + +The number of files parts parsed is now limited via the new +:setting:`DATA_UPLOAD_MAX_NUMBER_FILES` setting.
tests/handlers/test_exception.py+27 −1 modified@@ -1,6 +1,8 @@ from django.core.handlers.wsgi import WSGIHandler from django.test import SimpleTestCase, override_settings -from django.test.client import FakePayload +from django.test.client import ( + BOUNDARY, MULTIPART_CONTENT, FakePayload, encode_multipart, +) class ExceptionHandlerTests(SimpleTestCase): @@ -25,3 +27,27 @@ def test_data_upload_max_memory_size_exceeded(self): def test_data_upload_max_number_fields_exceeded(self): response = WSGIHandler()(self.get_suspicious_environ(), lambda *a, **k: None) self.assertEqual(response.status_code, 400) + + @override_settings(DATA_UPLOAD_MAX_NUMBER_FILES=2) + def test_data_upload_max_number_files_exceeded(self): + payload = FakePayload( + encode_multipart( + BOUNDARY, + { + "a.txt": "Hello World!", + "b.txt": "Hello Django!", + "c.txt": "Hello Python!", + }, + ) + ) + environ = { + "REQUEST_METHOD": "POST", + "CONTENT_TYPE": MULTIPART_CONTENT, + "CONTENT_LENGTH": len(payload), + "wsgi.input": payload, + "SERVER_NAME": "test", + "SERVER_PORT": "8000", + } + + response = WSGIHandler()(environ, lambda *a, **k: None) + self.assertEqual(response.status_code, 400)
tests/requests/test_data_upload_settings.py+50 −1 modified@@ -1,11 +1,14 @@ from io import BytesIO -from django.core.exceptions import RequestDataTooBig, TooManyFieldsSent +from django.core.exceptions import ( + RequestDataTooBig, TooManyFieldsSent, TooManyFilesSent, +) from django.core.handlers.wsgi import WSGIRequest from django.test import SimpleTestCase from django.test.client import FakePayload TOO_MANY_FIELDS_MSG = 'The number of GET/POST parameters exceeded settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.' +TOO_MANY_FILES_MSG = 'The number of files exceeded settings.DATA_UPLOAD_MAX_NUMBER_FILES.' TOO_MUCH_DATA_MSG = 'Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE.' @@ -166,6 +169,52 @@ def test_no_limit(self): self.request._load_post_and_files() +class DataUploadMaxNumberOfFilesMultipartPost(SimpleTestCase): + def setUp(self): + payload = FakePayload( + "\r\n".join( + [ + "--boundary", + ( + 'Content-Disposition: form-data; name="name1"; ' + 'filename="name1.txt"' + ), + "", + "value1", + "--boundary", + ( + 'Content-Disposition: form-data; name="name2"; ' + 'filename="name2.txt"' + ), + "", + "value2", + "--boundary--", + ] + ) + ) + self.request = WSGIRequest( + { + "REQUEST_METHOD": "POST", + "CONTENT_TYPE": "multipart/form-data; boundary=boundary", + "CONTENT_LENGTH": len(payload), + "wsgi.input": payload, + } + ) + + def test_number_exceeded(self): + with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=1): + with self.assertRaisesMessage(TooManyFilesSent, TOO_MANY_FILES_MSG): + self.request._load_post_and_files() + + def test_number_not_exceeded(self): + with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=2): + self.request._load_post_and_files() + + def test_no_limit(self): + with self.settings(DATA_UPLOAD_MAX_NUMBER_FILES=None): + self.request._load_post_and_files() + + class DataUploadMaxNumberOfFieldsFormPost(SimpleTestCase): def setUp(self): payload = FakePayload("\r\n".join(['a=1&a=2&a=3', '']))
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
31- github.com/advisories/GHSA-2hrw-hx67-34x6ghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/FKYVMMR7RPM6AHJ2SBVM2LO6D3NGFY7B/mitrevendor-advisory
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/HWY6DQWRVBALV73BPUVBXC3QIYUM24IK/mitrevendor-advisory
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/LTZVAKU5ALQWOKFTPISE257VCVIYGFQI/mitrevendor-advisory
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/VZS4G6NSZWPTVXMMZHJOJVQEPL3QTO77/mitrevendor-advisory
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/YJB6FUBBLVKKG655UMTLQNN6UQ6EDLSP/mitrevendor-advisory
- nvd.nist.gov/vuln/detail/CVE-2023-24580ghsaADVISORY
- www.openwall.com/lists/oss-security/2023/02/14/1ghsaWEB
- docs.djangoproject.com/en/4.1/releases/securityghsaWEB
- github.com/django/django/commit/628b33a854a9c68ec8a0c51f382f304a0044ec92ghsaWEB
- github.com/django/django/commit/83f1ea83e4553e211c1c5a0dfc197b66d4e50432ghsaWEB
- github.com/django/django/commit/a665ed5179f5bbd3db95ce67286d0192eff041d8ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2023-13.yamlghsaWEB
- groups.google.com/forum/ghsaWEB
- groups.google.com/forum/ghsaWEB
- lists.debian.org/debian-lts-announce/2023/02/msg00023.htmlghsamailing-listWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/FKYVMMR7RPM6AHJ2SBVM2LO6D3NGFY7BghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/HWY6DQWRVBALV73BPUVBXC3QIYUM24IKghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/LTZVAKU5ALQWOKFTPISE257VCVIYGFQIghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/VZS4G6NSZWPTVXMMZHJOJVQEPL3QTO77ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/YJB6FUBBLVKKG655UMTLQNN6UQ6EDLSPghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/FKYVMMR7RPM6AHJ2SBVM2LO6D3NGFY7BghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/HWY6DQWRVBALV73BPUVBXC3QIYUM24IKghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/LTZVAKU5ALQWOKFTPISE257VCVIYGFQIghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/VZS4G6NSZWPTVXMMZHJOJVQEPL3QTO77ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/YJB6FUBBLVKKG655UMTLQNN6UQ6EDLSPghsaWEB
- security.netapp.com/advisory/ntap-20230316-0006ghsaWEB
- www.djangoproject.com/weblog/2023/feb/14/security-releasesghsaWEB
- docs.djangoproject.com/en/4.1/releases/security/mitre
- security.netapp.com/advisory/ntap-20230316-0006/mitre
- www.djangoproject.com/weblog/2023/feb/14/security-releases/mitre
News mentions
0No linked articles in our index yet.