From 6e2b684b61e9cc6d8346fe06b2495b10b0237e9e Mon Sep 17 00:00:00 2001 From: starlet-dx <15929766099@163.com> Date: Mon, 14 Aug 2023 16:21:13 +0800 Subject: [PATCH] Fix CVE-2023-23934 and CVE-2023-25577 (cherry picked from commit a28c3782e3a95a6bb9e97f75cd0eb29523573403) --- CVE-2023-23934.patch | 73 ++++++++++++++++++++++ CVE-2023-25577.patch | 146 +++++++++++++++++++++++++++++++++++++++++++ python-werkzeug.spec | 7 ++- 3 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 CVE-2023-23934.patch create mode 100644 CVE-2023-25577.patch diff --git a/CVE-2023-23934.patch b/CVE-2023-23934.patch new file mode 100644 index 0000000..78e4634 --- /dev/null +++ b/CVE-2023-23934.patch @@ -0,0 +1,73 @@ +From ff93ffb858db15ec70ba57b7850cb9cb01d531c8 Mon Sep 17 00:00:00 2001 +From: starlet-dx <15929766099@163.com> +Date: Tue, 15 Aug 2023 09:56:10 +0800 +Subject: [PATCH 1/1] don't strip leading = when parsing cookie + +Origin: +https://github.com/pallets/werkzeug/commit/8c2b4b82d0cade0d37e6a88e2cd2413878e8ebd4 +--- + src/werkzeug/_internal.py | 13 +++++++++---- + tests/test_http.py | 2 ++ + 2 files changed, 11 insertions(+), 4 deletions(-) + +diff --git a/src/werkzeug/_internal.py b/src/werkzeug/_internal.py +index 1d2eaf5..fe69ccb 100644 +--- a/src/werkzeug/_internal.py ++++ b/src/werkzeug/_internal.py +@@ -40,7 +40,7 @@ _quote_re = re.compile(br"[\\].") + _legal_cookie_chars_re = br"[\w\d!#%&\'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]" + _cookie_re = re.compile( + br""" +- (?P[^=;]+) ++ (?P[^=;]*) + (?:\s*=\s* + (?P + "(?:[^\\"]|\\.)*" | +@@ -316,16 +316,21 @@ def _cookie_parse_impl(b): + """Lowlevel cookie parsing facility that operates on bytes.""" + i = 0 + n = len(b) ++ b += b";" + + while i < n: +- match = _cookie_re.search(b + b";", i) ++ match = _cookie_re.match(b, i) ++ + if not match: + break + +- key = match.group("key").strip() +- value = match.group("val") or b"" + i = match.end(0) ++ key = match.group("key").strip() ++ ++ if not key: ++ continue + ++ value = match.group("val") or b"" + yield _cookie_unquote(key), _cookie_unquote(value) + + +diff --git a/tests/test_http.py b/tests/test_http.py +index 5725170..58042c0 100644 +--- a/tests/test_http.py ++++ b/tests/test_http.py +@@ -446,6 +446,7 @@ class TestHTTPUtility(object): + cookies = http.parse_cookie( + "dismiss-top=6; CP=null*; PHPSESSID=0a539d42abc001cdc762809248d4beed;" + ' a=42; b="\\";"; ; fo234{=bar;blub=Blah;' ++ "==__Host-eq=bad;__Host-eq=good;" + ) + assert cookies.to_dict() == { + "CP": u"null*", +@@ -455,6 +456,7 @@ class TestHTTPUtility(object): + "b": u'";', + "fo234{": u"bar", + "blub": u"Blah", ++ "__Host-eq": "good", + } + + def test_dump_cookie(self): +-- +2.30.0 + diff --git a/CVE-2023-25577.patch b/CVE-2023-25577.patch new file mode 100644 index 0000000..949157a --- /dev/null +++ b/CVE-2023-25577.patch @@ -0,0 +1,146 @@ +From dbd3af18b9e7e6e081181594fd6a9fb8daa01a91 Mon Sep 17 00:00:00 2001 +From: starlet-dx <15929766099@163.com> +Date: Tue, 15 Aug 2023 10:04:11 +0800 +Subject: [PATCH 1/1] limit the maximum number of multipart form parts + +Origin: +https://github.com/pallets/werkzeug/commit/fe899d0cdf767a7289a8bf746b7f72c2907a1b4b +--- + src/werkzeug/formparser.py | 16 ++++++++++++++-- + src/werkzeug/wrappers/base_request.py | 8 ++++++++ + tests/test_formparser.py | 9 +++++++++ + 3 files changed, 31 insertions(+), 2 deletions(-) + +diff --git a/src/werkzeug/formparser.py b/src/werkzeug/formparser.py +index ffdb9b0..40959fb 100644 +--- a/src/werkzeug/formparser.py ++++ b/src/werkzeug/formparser.py +@@ -168,6 +168,8 @@ class FormDataParser(object): + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param silent: If set to False parsing errors will not be caught. ++ :param max_form_parts: The maximum number of parts to be parsed. If this is ++ exceeded, a :exc:`~exceptions.RequestEntityTooLarge` exception is raised. + """ + + def __init__( +@@ -179,6 +181,7 @@ class FormDataParser(object): + max_content_length=None, + cls=None, + silent=True, ++ max_form_parts=None, + ): + if stream_factory is None: + stream_factory = default_stream_factory +@@ -191,6 +194,7 @@ class FormDataParser(object): + cls = MultiDict + self.cls = cls + self.silent = silent ++ self.max_form_parts = max_form_parts + + def get_parse_func(self, mimetype, options): + return self.parse_functions.get(mimetype) +@@ -244,6 +248,7 @@ class FormDataParser(object): + self.errors, + max_form_memory_size=self.max_form_memory_size, + cls=self.cls, ++ max_form_parts=self.max_form_parts, + ) + boundary = options.get("boundary") + if boundary is None: +@@ -333,10 +338,12 @@ class MultiPartParser(object): + max_form_memory_size=None, + cls=None, + buffer_size=64 * 1024, ++ max_form_parts=None, + ): + self.charset = charset + self.errors = errors + self.max_form_memory_size = max_form_memory_size ++ self.max_form_parts = max_form_parts + self.stream_factory = ( + default_stream_factory if stream_factory is None else stream_factory + ) +@@ -528,11 +535,12 @@ class MultiPartParser(object): + + yield _end, None + +- def parse_parts(self, file, boundary, content_length): ++ def parse_parts(self, file, boundary, content_length, max_parts=None): + """Generate ``('file', (name, val))`` and + ``('form', (name, val))`` parts. + """ + in_memory = 0 ++ _parts_decoded = 0 + + for ellt, ell in self.parse_lines(file, boundary, content_length): + if ellt == _begin_file: +@@ -562,6 +570,9 @@ class MultiPartParser(object): + self.in_memory_threshold_reached(in_memory) + + elif ellt == _end: ++ _parts_decoded += 1 ++ if max_parts is not None and _parts_decoded > max_parts: ++ raise exceptions.RequestEntityTooLarge() + if is_file: + container.seek(0) + yield ( +@@ -577,7 +588,8 @@ class MultiPartParser(object): + + def parse(self, file, boundary, content_length): + formstream, filestream = tee( +- self.parse_parts(file, boundary, content_length), 2 ++ self.parse_parts(file, boundary, content_length, ++ max_parts=self.max_form_parts), 2 + ) + form = (p[1] for p in formstream if p[0] == "form") + files = (p[1] for p in filestream if p[0] == "file") +diff --git a/src/werkzeug/wrappers/base_request.py b/src/werkzeug/wrappers/base_request.py +index 1f21db2..0c1e221 100644 +--- a/src/werkzeug/wrappers/base_request.py ++++ b/src/werkzeug/wrappers/base_request.py +@@ -98,6 +98,13 @@ class BaseRequest(object): + #: .. versionadded:: 0.5 + max_form_memory_size = None + ++ #: The maximum number of multipart parts to parse, passed to ++ #: :attr:`form_data_parser_class`. Parsing form data with more than this ++ #: many parts will raise :exc:`~.RequestEntityTooLarge`. ++ #: ++ #: .. versionadded:: 2.2.3 ++ max_form_parts = 1000 ++ + #: the class to use for `args` and `form`. The default is an + #: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports + #: multiple values per key. alternatively it makes sense to use an +@@ -293,6 +300,7 @@ class BaseRequest(object): + self.max_form_memory_size, + self.max_content_length, + self.parameter_storage_class, ++ max_form_parts=self.max_form_parts, + ) + + def _load_form_data(self): +diff --git a/tests/test_formparser.py b/tests/test_formparser.py +index 6d85838..2a8d635 100644 +--- a/tests/test_formparser.py ++++ b/tests/test_formparser.py +@@ -124,6 +124,15 @@ class TestFormParser(object): + req.max_form_memory_size = 400 + strict_eq(req.form["foo"], u"Hello World") + ++ req = Request.from_values( ++ input_stream=io.BytesIO(data), ++ content_length=len(data), ++ content_type="multipart/form-data; boundary=foo", ++ method="POST", ++ ) ++ req.max_form_parts = 1 ++ pytest.raises(RequestEntityTooLarge, lambda: req.form["foo"]) ++ + def test_missing_multipart_boundary(self): + data = ( + b"--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\n" +-- +2.30.0 + diff --git a/python-werkzeug.spec b/python-werkzeug.spec index bb656b3..4620234 100644 --- a/python-werkzeug.spec +++ b/python-werkzeug.spec @@ -3,11 +3,13 @@ Name: python-werkzeug Summary: A comprehensive WSGI web application library Version: 1.0.1 -Release: 1 +Release: 2 License: BSD URL: https://github.com/pallets/werkzeug Source0: https://github.com/pallets/werkzeug/archive/1.0.1.tar.gz Patch0: Disable-tests-because-need-pytest-3.9.patch +Patch1: CVE-2023-23934.patch +Patch2: CVE-2023-25577.patch BuildArch: noarch %global _description\ @@ -123,6 +125,9 @@ popd %changelog +* Mon Aug 14 2023 yaoxin - 1.0.1-2 +- Fix CVE-2023-23934 and CVE-2023-25577 + * Tue Aug 17 2021 huanghaitao - 1.0.1-1 - Update to 1.0.1 - Disable tests because need pytest-3.9 -- Gitee