summaryrefslogtreecommitdiffstats
path: root/meta-python/recipes-devtools/python/python3-werkzeug/CVE-2024-34069-0001.patch
blob: 74b39df3a36aa4eb4b1de5412fb563cd351eabcd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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.

CVE: CVE-2024-34069

Upstream-Status: Backport [https://github.com/pallets/werkzeug/commit/71b69dfb7df3d912e66bab87fbb1f21f83504967]

Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
---
 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 49001e0..87e68c4 100644
--- a/src/werkzeug/debug/__init__.py
+++ b/src/werkzeug/debug/__init__.py
@@ -290,6 +290,14 @@ class DebuggedApplication:
             self._pin, self._pin_cookie = pin_cookie  # type: ignore
         return self._pin

+        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
+        """
+
     @pin.setter
     def pin(self, value: str) -> None:
         self._pin = value
@@ -475,6 +483,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 a19d4bd..84b0664 100644
--- a/src/werkzeug/serving.py
+++ b/src/werkzeug/serving.py
@@ -1038,6 +1038,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():
         s = prepare_socket(hostname, port)
--
2.40.0