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

CVE: CVE-2024-34069

Upstream-Status: Backport [https://github.com/pallets/werkzeug/commit/890b6b62634fa61224222aee31081c61b054ff01]

Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
---
 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 87e68c4..0302b24 100644
--- a/src/werkzeug/debug/__init__.py
+++ b/src/werkzeug/debug/__init__.py
@@ -16,7 +16,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
@@ -331,7 +333,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,
             )
@@ -359,10 +361,16 @@ 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]
+
         return Response(frame.eval(command), mimetype="text/html")

     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 = {}
@@ -411,12 +419,18 @@ class DebuggedApplication:
             return None
         return (time.time() - PIN_TIME) < int(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)
@@ -466,8 +480,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:"
@@ -483,8 +500,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")
@@ -496,7 +511,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 1b4d892..7e7b4d2 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.40.0
