From 890b6b62634fa61224222aee31081c61b054ff01 Mon Sep 17 00:00:00 2001 From: David Lord 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 cda1fa2e7..6bef30fbc 100644 --- a/src/werkzeug/debug/__init__.py +++ b/src/werkzeug/debug/__init__.py @@ -19,7 +19,9 @@ 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 @@ -352,7 +354,7 @@ def debug_application( 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, ) @@ -380,6 +382,9 @@ def execute_command( # type: ignore[return] frame: 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: @@ -390,6 +395,9 @@ def execute_command( # type: ignore[return] 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 = {} @@ -442,12 +450,18 @@ def check_pin_trust(self, environ: WSGIEnvironment) -> bool | None: 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) @@ -497,8 +511,11 @@ def pin_auth(self, request: Request) -> Response: 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:" @@ -514,8 +531,6 @@ def __call__( # 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") @@ -527,7 +542,7 @@ def __call__( 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 48ec1bfa0..14fa0ac88 100644 --- a/src/werkzeug/sansio/utils.py +++ b/src/werkzeug/sansio/utils.py @@ -8,7 +8,7 @@ from ..urls import uri_to_iri -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.