Compare commits

...

10 Commits

Author SHA1 Message Date
openeuler-ci-bot
2e0194d9db
!47 [sync] PR-45: Fix CVE-2024-49767
From: @openeuler-sync-bot 
Reviewed-by: @lyn1001 
Signed-off-by: @lyn1001
2024-11-12 01:35:24 +00:00
liningjie
f633b8388e Fix CVE-2024-49767
(cherry picked from commit c7566002c0a7d671ce568caacd3be66f7a70d536)
2024-11-11 09:50:31 +08:00
openeuler-ci-bot
9700a2e751
!41 fix CVE-2024-34069
From: @yinyongkang 
Reviewed-by: @starlet-dx, @caodongxia 
Signed-off-by: @caodongxia
2024-05-10 03:06:53 +00:00
yinyongkang
0048ebc37a fix CVE-2024-34069 2024-05-08 15:34:09 +08:00
openeuler-ci-bot
a926306299
!31 Update to 2.2.3
From: @wu-leilei 
Reviewed-by: @lyn1001, @caodongxia 
Signed-off-by: @caodongxia
2023-05-10 07:38:25 +00:00
wu-leilei
9020e9e70b Update to 2.2.3 2023-05-10 14:46:54 +08:00
openeuler-ci-bot
04eacdd4b6
!22 fix typo and grammar mistake
From: @Bolehu 
Reviewed-by: @Charlie_li 
Signed-off-by: @Charlie_li
2023-01-09 06:18:37 +00:00
Bolehu
d5e619a3ac fix typo and grammar mistake
Signed-off-by: Bolehu <heyaohua@xfusion.com>
2023-01-06 17:37:21 +08:00
openeuler-ci-bot
e0a8cc65b6
!19 升级python-werkzeug版本为2.0.3
From: @jiangpengjuj 
Reviewed-by: @ruebb, @small_leek 
Signed-off-by: @ruebb, @small_leek
2022-06-22 08:11:51 +00:00
jiangpengjuj
ea1ca248ad 升级python-werkzeug版本为2.0.3 2022-06-22 09:27:40 +08:00
7 changed files with 452 additions and 22 deletions

Binary file not shown.

View File

@ -0,0 +1,119 @@
From 890b6b62634fa61224222aee31081c61b054ff01 Mon Sep 17 00:00:00 2001
From: David Lord <davidism@gmail.com>
Date: Fri, 3 May 2024 14:49:43 -0700
Subject: [PATCH] only require trusted host for evalex
---
src/werkzeug/debug/__init__.py | 25 ++++++++++++++++++++-----
src/werkzeug/sansio/utils.py | 2 +-
2 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/src/werkzeug/debug/__init__.py b/src/werkzeug/debug/__init__.py
index e779fd9..8952342 100644
--- a/src/werkzeug/debug/__init__.py
+++ b/src/werkzeug/debug/__init__.py
@@ -18,7 +18,9 @@ from zlib import adler32
from .._internal import _log
from ..exceptions import NotFound
+from ..exceptions import SecurityError
from ..http import parse_cookie
+from ..sansio.utils import host_is_trusted
from ..security import gen_salt
from ..utils import send_file
from ..wrappers.request import Request
@@ -350,7 +352,7 @@ class DebuggedApplication:
is_trusted = bool(self.check_pin_trust(environ))
html = tb.render_debugger_html(
- evalex=self.evalex,
+ evalex=self.evalex and self.check_host_trust(environ),
secret=self.secret,
evalex_trusted=is_trusted,
)
@@ -378,6 +380,9 @@ class DebuggedApplication:
frame: t.Union[DebugFrameSummary, _ConsoleFrame],
) -> Response:
"""Execute a command in a console."""
+ if not self.check_host_trust(request.environ):
+ return SecurityError() # type: ignore[return-value]
+
contexts = self.frame_contexts.get(id(frame), [])
with ExitStack() as exit_stack:
@@ -388,6 +393,9 @@ class DebuggedApplication:
def display_console(self, request: Request) -> Response:
"""Display a standalone shell."""
+ if not self.check_host_trust(request.environ):
+ return SecurityError() # type: ignore[return-value]
+
if 0 not in self.frames:
if self.console_init_func is None:
ns = {}
@@ -440,12 +448,18 @@ class DebuggedApplication:
return None
return (time.time() - PIN_TIME) < ts
+ def check_host_trust(self, environ: "WSGIEnvironment") -> bool:
+ return host_is_trusted(environ.get("HTTP_HOST"), self.trusted_hosts)
+
def _fail_pin_auth(self) -> None:
time.sleep(5.0 if self._failed_pin_auth > 5 else 0.5)
self._failed_pin_auth += 1
def pin_auth(self, request: Request) -> Response:
"""Authenticates with the pin."""
+ if not self.check_host_trust(request.environ):
+ return SecurityError() # type: ignore[return-value]
+
exhausted = False
auth = False
trust = self.check_pin_trust(request.environ)
@@ -495,8 +509,11 @@ class DebuggedApplication:
rv.delete_cookie(self.pin_cookie_name)
return rv
- def log_pin_request(self) -> Response:
+ def log_pin_request(self, request: Request) -> Response:
"""Log the pin if needed."""
+ if not self.check_host_trust(request.environ):
+ return SecurityError() # type: ignore[return-value]
+
if self.pin_logging and self.pin is not None:
_log(
"info", " * To enable the debugger you need to enter the security pin:"
@@ -512,8 +529,6 @@ class DebuggedApplication:
# form data! Otherwise the application won't have access to that data
# any more!
request = Request(environ)
- request.trusted_hosts = self.trusted_hosts
- assert request.host # will raise 400 error if not trusted
response = self.debug_application
if request.args.get("__debugger__") == "yes":
cmd = request.args.get("cmd")
@@ -525,7 +540,7 @@ class DebuggedApplication:
elif cmd == "pinauth" and secret == self.secret:
response = self.pin_auth(request) # type: ignore
elif cmd == "printpin" and secret == self.secret:
- response = self.log_pin_request() # type: ignore
+ response = self.log_pin_request(request) # type: ignore
elif (
self.evalex
and cmd is not None
diff --git a/src/werkzeug/sansio/utils.py b/src/werkzeug/sansio/utils.py
index e639dcb..468f926 100644
--- a/src/werkzeug/sansio/utils.py
+++ b/src/werkzeug/sansio/utils.py
@@ -6,7 +6,7 @@ from ..urls import uri_to_iri
from ..urls import url_quote
-def host_is_trusted(hostname: str, trusted_list: t.Iterable[str]) -> bool:
+def host_is_trusted(hostname: str | None, trusted_list: t.Iterable[str]) -> bool:
"""Check if a host matches a list of trusted names.
:param hostname: The name to check.
--
2.41.0

View File

@ -0,0 +1,144 @@
From 71b69dfb7df3d912e66bab87fbb1f21f83504967 Mon Sep 17 00:00:00 2001
From: David Lord <davidism@gmail.com>
Date: Thu, 2 May 2024 11:55:52 -0700
Subject: [PATCH] restrict debugger trusted hosts
Add a list of `trusted_hosts` to the `DebuggedApplication` middleware. It defaults to only allowing `localhost`, `.localhost` subdomains, and `127.0.0.1`. `run_simple(use_debugger=True)` adds its `hostname` argument to the trusted list as well. The middleware can be used directly to further modify the trusted list in less common development scenarios.
The debugger UI uses the full `document.location` instead of only `document.location.pathname`.
Either of these fixes on their own mitigates the reported vulnerability.
---
docs/debug.rst | 35 +++++++++++++++++++++++----
src/werkzeug/debug/__init__.py | 10 ++++++++
src/werkzeug/debug/shared/debugger.js | 4 +--
src/werkzeug/serving.py | 3 +++
4 files changed, 45 insertions(+), 7 deletions(-)
diff --git a/docs/debug.rst b/docs/debug.rst
index 25a9f0b..d842135 100644
--- a/docs/debug.rst
+++ b/docs/debug.rst
@@ -16,7 +16,8 @@ interactive debug console to execute code in any frame.
The debugger allows the execution of arbitrary code which makes it a
major security risk. **The debugger must never be used on production
machines. We cannot stress this enough. Do not enable the debugger
- in production.**
+ in production.** Production means anything that is not development,
+ and anything that is publicly accessible.
.. note::
@@ -72,10 +73,9 @@ argument to get a detailed list of all the attributes it has.
Debugger PIN
------------
-Starting with Werkzeug 0.11 the debug console is protected by a PIN.
-This is a security helper to make it less likely for the debugger to be
-exploited if you forget to disable it when deploying to production. The
-PIN based authentication is enabled by default.
+The debug console is protected by a PIN. This is a security helper to make it
+less likely for the debugger to be exploited if you forget to disable it when
+deploying to production. The PIN based authentication is enabled by default.
The first time a console is opened, a dialog will prompt for a PIN that
is printed to the command line. The PIN is generated in a stable way
@@ -92,6 +92,31 @@ intended to make it harder for an attacker to exploit the debugger.
Never enable the debugger in production.**
+Allowed Hosts
+-------------
+
+The debug console will only be served if the request comes from a trusted host.
+If a request comes from a browser page that is not served on a trusted URL, a
+400 error will be returned.
+
+By default, ``localhost``, any ``.localhost`` subdomain, and ``127.0.0.1`` are
+trusted. ``run_simple`` will trust its ``hostname`` argument as well. To change
+this further, use the debug middleware directly rather than through
+``use_debugger=True``.
+
+.. code-block:: python
+
+ if os.environ.get("USE_DEBUGGER") in {"1", "true"}:
+ app = DebuggedApplication(app, evalex=True)
+ app.trusted_hosts = [...]
+
+ run_simple("localhost", 8080, app)
+
+**This feature is not meant to entirely secure the debugger. It is
+intended to make it harder for an attacker to exploit the debugger.
+Never enable the debugger in production.**
+
+
Pasting Errors
--------------
diff --git a/src/werkzeug/debug/__init__.py b/src/werkzeug/debug/__init__.py
index 24d19bb..e779fd9 100644
--- a/src/werkzeug/debug/__init__.py
+++ b/src/werkzeug/debug/__init__.py
@@ -296,6 +296,14 @@ class DebuggedApplication:
else:
self.pin = None
+ self.trusted_hosts: list[str] = [".localhost", "127.0.0.1"]
+ """List of domains to allow requests to the debugger from. A leading dot
+ allows all subdomains. This only allows ``".localhost"`` domains by
+ default.
+
+ .. versionadded:: 3.0.3
+ """
+
@property
def pin(self) -> t.Optional[str]:
if not hasattr(self, "_pin"):
@@ -504,6 +512,8 @@ class DebuggedApplication:
# form data! Otherwise the application won't have access to that data
# any more!
request = Request(environ)
+ request.trusted_hosts = self.trusted_hosts
+ assert request.host # will raise 400 error if not trusted
response = self.debug_application
if request.args.get("__debugger__") == "yes":
cmd = request.args.get("cmd")
diff --git a/src/werkzeug/debug/shared/debugger.js b/src/werkzeug/debug/shared/debugger.js
index 2354f03..bee079f 100644
--- a/src/werkzeug/debug/shared/debugger.js
+++ b/src/werkzeug/debug/shared/debugger.js
@@ -48,7 +48,7 @@ function initPinBox() {
btn.disabled = true;
fetch(
- `${document.location.pathname}?__debugger__=yes&cmd=pinauth&pin=${pin}&s=${encodedSecret}`
+ `${document.location}?__debugger__=yes&cmd=pinauth&pin=${pin}&s=${encodedSecret}`
)
.then((res) => res.json())
.then(({auth, exhausted}) => {
@@ -79,7 +79,7 @@ function promptForPin() {
if (!EVALEX_TRUSTED) {
const encodedSecret = encodeURIComponent(SECRET);
fetch(
- `${document.location.pathname}?__debugger__=yes&cmd=printpin&s=${encodedSecret}`
+ `${document.location}?__debugger__=yes&cmd=printpin&s=${encodedSecret}`
);
const pinPrompt = document.getElementsByClassName("pin-prompt")[0];
fadeIn(pinPrompt);
diff --git a/src/werkzeug/serving.py b/src/werkzeug/serving.py
index 2a2e74d..19ed250 100644
--- a/src/werkzeug/serving.py
+++ b/src/werkzeug/serving.py
@@ -1028,6 +1028,9 @@ def run_simple(
from .debug import DebuggedApplication
application = DebuggedApplication(application, evalex=use_evalex)
+ # Allow the specified hostname to use the debugger, in addition to
+ # localhost domains.
+ application.trusted_hosts.append(hostname)
if not is_running_from_reloader():
fd = None
--
2.41.0

View File

@ -0,0 +1,83 @@
From 8760275afb72bd10b57d92cb4d52abf759b2f3a7 Mon Sep 17 00:00:00 2001
From: David Lord <davidism@gmail.com>
Date: Fri, 25 Oct 2024 06:46:50 -0700
Subject: [PATCH] apply max_form_memory_size another level up in the parser
---
src/werkzeug/formparser.py | 11 +++++++++++
src/werkzeug/sansio/multipart.py | 2 ++
tests/test_formparser.py | 12 ++++++++++++
3 files changed, 25 insertions(+)
diff --git a/src/werkzeug/formparser.py b/src/werkzeug/formparser.py
index bebb2fc..b82af82 100644
--- a/src/werkzeug/formparser.py
+++ b/src/werkzeug/formparser.py
@@ -405,6 +405,7 @@ class MultiPartParser:
def parse(
self, stream: t.IO[bytes], boundary: bytes, content_length: t.Optional[int]
) -> t.Tuple[MultiDict, MultiDict]:
+ field_size: int | None = None
container: t.Union[t.IO[bytes], t.List[bytes]]
_write: t.Callable[[bytes], t.Any]
@@ -431,13 +432,23 @@ class MultiPartParser:
while not isinstance(event, (Epilogue, NeedData)):
if isinstance(event, Field):
current_part = event
+ field_size = 0
container = []
_write = container.append
elif isinstance(event, File):
current_part = event
+ field_size = None
container = self.start_file_streaming(event, content_length)
_write = container.write
elif isinstance(event, Data):
+ if self.max_form_memory_size is not None and field_size is not None:
+ # Ensure that accumulated data events do not exceed limit.
+ # Also checked within single event in MultipartDecoder.
+ field_size += len(event.data)
+
+ if field_size > self.max_form_memory_size:
+ raise RequestEntityTooLarge()
+
_write(event.data)
if not event.more_data:
if isinstance(current_part, Field):
diff --git a/src/werkzeug/sansio/multipart.py b/src/werkzeug/sansio/multipart.py
index 2684e5d..e2b0e79 100644
--- a/src/werkzeug/sansio/multipart.py
+++ b/src/werkzeug/sansio/multipart.py
@@ -142,6 +142,8 @@ class MultipartDecoder:
self.max_form_memory_size is not None
and len(self.buffer) + len(data) > self.max_form_memory_size
):
+ # Ensure that data within single event does not exceed limit.
+ # Also checked across accumulated events in MultiPartParser.
raise RequestEntityTooLarge()
else:
self.buffer.extend(data)
diff --git a/tests/test_formparser.py b/tests/test_formparser.py
index 4c518b1..05fa84e 100644
--- a/tests/test_formparser.py
+++ b/tests/test_formparser.py
@@ -455,3 +455,15 @@ class TestMultiPartParser:
) as request:
assert request.files["rfc2231"].filename == "a b c d e f.txt"
assert request.files["rfc2231"].read() == b"file contents"
+
+
+def test_multipart_max_form_memory_size() -> None:
+ """max_form_memory_size is tracked across multiple data events."""
+ data = b"--bound\r\nContent-Disposition: form-field; name=a\r\n\r\n"
+ data += b"a" * 15 + b"\r\n--bound--"
+ # The buffer size is less than the max size, so multiple data events will be
+ # returned. The field size is greater than the max.
+ parser = formparser.MultiPartParser(max_form_memory_size=10, buffer_size=5)
+
+ with pytest.raises(RequestEntityTooLarge):
+ parser.parse(io.BytesIO(data), b"bound", None)
--
2.33.0

BIN
Werkzeug-2.2.3.tar.gz Normal file

Binary file not shown.

61
ephemeral_port_reserve.py Executable file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env python
from __future__ import absolute_import
from __future__ import unicode_literals
import contextlib
import errno
from socket import error as SocketError
from socket import SO_REUSEADDR
from socket import socket
from socket import SOL_SOCKET
LOCALHOST = '127.0.0.1'
def reserve(ip=LOCALHOST, port=0):
"""Bind to an ephemeral port, force it into the TIME_WAIT state, and unbind it.
This means that further ephemeral port alloctions won't pick this "reserved" port,
but subprocesses can still bind to it explicitly, given that they use SO_REUSEADDR.
By default on linux you have a grace period of 60 seconds to reuse this port.
To check your own particular value:
$ cat /proc/sys/net/ipv4/tcp_fin_timeout
60
By default, the port will be reserved for localhost (aka 127.0.0.1).
To reserve a port for a different ip, provide the ip as the first argument.
Note that IP 0.0.0.0 is interpreted as localhost.
"""
port = int(port)
with contextlib.closing(socket()) as s:
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
try:
s.bind((ip, port))
except SocketError as e:
# socket.error: EADDRINUSE Address already in use
if e.errno == errno.EADDRINUSE and port != 0:
s.bind((ip, 0))
else:
raise
# the connect below deadlocks on kernel >= 4.4.0 unless this arg is greater than zero
s.listen(1)
sockname = s.getsockname()
# these three are necessary just to get the port into a TIME_WAIT state
with contextlib.closing(socket()) as s2:
s2.connect(sockname)
sock, _ = s.accept()
with contextlib.closing(sock):
return sockname[1]
def main(): # pragma: no cover
from sys import argv
port = reserve(*argv[1:])
print(port)
if __name__ == '__main__':
exit(main())

View File

@ -1,16 +1,23 @@
%global _empty_manifest_terminate_build 0
Name: python-werkzeug
Version: 1.0.1
Release: 2
Version: 2.2.3
Release: 3
Summary: The comprehensive WSGI web application library.
License: BSD-3-Clause
URL: https://palletsprojects.com/p/werkzeug/
Source0: https://github.com/pallets/werkzeug/archive/1.0.1.tar.gz
BuildArch: noarch
Source0: https://files.pythonhosted.org/packages/source/W/Werkzeug/Werkzeug-2.2.3.tar.gz
# for test
Source1: https://github.com/Yelp/ephemeral-port-reserve/blob/master/ephemeral_port_reserve.py
BuildRequires: python3-werkzeug
Patch01: CVE-2024-34069-restrict-debugger-trusted-hosts.patch
Patch02: CVE-2024-34069-only-require-trusted-host-for-evalex.patch
Patch03: CVE-2024-49767--apply-max_form_memory_size-another-level-up.patch
BuildArch: noarch
BuildRequires: python3-werkzeug python3-markupsafe
Requires: python3-pytest
Requires: python3-pytest-xprocess
Requires: python3-pytest-timeout
Requires: python3-coverage
Requires: python3-tox
@ -41,10 +48,10 @@ It includes:
locally.
- A test client for simulating HTTP requests during testing without
requiring running a server.
Werkzeug is Unicode aware and doesn't enforce any dependencies. It is up
to the developer to choose a template engine, database adapter, and even
how to handle requests. It can be used to build all sorts of end user
applications such as blogs, wikis, or bulletin boards.
Werkzeug doesn't enforce any dependencies. It is up to the developer to
choose a template engine, database adapter, and even how to handle
requests. It can be used to build all sorts of end user applications
such as blogs, wikis, or bulletin boards.
`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while
providing more structure and patterns for defining powerful
applications.
@ -52,13 +59,14 @@ applications.
%package -n python3-werkzeug
Summary: The comprehensive WSGI web application library.
Provides: python-werkzeug
BuildRequires: python3-devel
BuildRequires: python3-pytest
BuildRequires: python3-pytest-xprocess
BuildRequires: python3-pytest-timeout
BuildRequires: python3-requests
BuildRequires: python3-setuptools
BuildRequires: python3-devel
BuildRequires: python3-cryptography
BuildRequires: python3-greenlet
BuildRequires: python3-setuptools
%description -n python3-werkzeug
*werkzeug* German noun: "tool". Etymology: *werk* ("work"), *zeug* ("stuff")
Werkzeug is a comprehensive `WSGI`_ web application library. It began as
@ -81,10 +89,10 @@ It includes:
locally.
- A test client for simulating HTTP requests during testing without
requiring running a server.
Werkzeug is Unicode aware and doesn't enforce any dependencies. It is up
to the developer to choose a template engine, database adapter, and even
how to handle requests. It can be used to build all sorts of end user
applications such as blogs, wikis, or bulletin boards.
Werkzeug doesn't enforce any dependencies. It is up to the developer to
choose a template engine, database adapter, and even how to handle
requests. It can be used to build all sorts of end user applications
such as blogs, wikis, or bulletin boards.
`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while
providing more structure and patterns for defining powerful
applications.
@ -114,16 +122,17 @@ It includes:
locally.
- A test client for simulating HTTP requests during testing without
requiring running a server.
Werkzeug is Unicode aware and doesn't enforce any dependencies. It is up
to the developer to choose a template engine, database adapter, and even
how to handle requests. It can be used to build all sorts of end user
applications such as blogs, wikis, or bulletin boards.
Werkzeug doesn't enforce any dependencies. It is up to the developer to
choose a template engine, database adapter, and even how to handle
requests. It can be used to build all sorts of end user applications
such as blogs, wikis, or bulletin boards.
`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while
providing more structure and patterns for defining powerful
applications.
%prep
%autosetup -n werkzeug-1.0.1
%autosetup -n Werkzeug-%{version} -p1
cp %{SOURCE1} %{_builddir}/Werkzeug-%{version}/tests/
%build
%py3_build
@ -157,8 +166,7 @@ mv %{buildroot}/filelist.lst .
mv %{buildroot}/doclist.lst .
%check
PYTHONPATH=%{buildroot}%{python3_sitelib}
/usr/bin/pytest -p no:unraisableexception
PYTHONPATH=%{buildroot}%{python3_sitelib} pytest -k 'not (test_serving)'
%files -n python3-werkzeug -f filelist.lst
%dir %{python3_sitelib}/*
@ -167,6 +175,21 @@ PYTHONPATH=%{buildroot}%{python3_sitelib}
%{_docdir}/*
%changelog
* Sat Oct 26 2024 liningjie <liningjie@xfusion.com> - 2.2.3-3
- Fix CVE-2024-49767
* Tue May 07 2024 yinyongkang <yinyongkang@kylinos.cn> - 2.2.3-2
- fix CVE-2024-34069
* Tue May 09 2023 wulei <wu_lei@hoperun.com> - 2.2.3-1
- Update to 2.2.3
* Sat Jan 7 2023 Bolehu <heyaohua@xfusion.com> - 2.0.3-2
- fix typo and grammar mistake
* Fri Jun 17 2022 jiangpengju <jiangpengju2@h-partners.com> - 2.0.3-1
- Upgrade python-werkzeug version to 2.0.3
* Mon Nov 15 2021 xu_ping <xuping33@huawei.com>-1.0.1-2
- fix test failures due to unhandled exceptions being thrown without being propagated to caller.