summaryrefslogtreecommitdiffstats
path: root/meta-python
diff options
context:
space:
mode:
Diffstat (limited to 'meta-python')
-rw-r--r--meta-python/recipes-devtools/python/python3-aiohttp/CVE-2024-23334.patch222
-rw-r--r--meta-python/recipes-devtools/python/python3-aiohttp_3.8.6.bb3
2 files changed, 225 insertions, 0 deletions
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2024-23334.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2024-23334.patch
new file mode 100644
index 0000000000..29909529aa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2024-23334.patch
@@ -0,0 +1,222 @@
1From 1c335944d6a8b1298baf179b7c0b3069f10c514b
2From: Sam Bull <git@sambull.org>
3Date: Sun Jan 28 18:13:06 2024 +0000
4Subject: [PATCH] python3-aiohttp: Validate static paths (#8079)
5
6Co-authored-by: J. Nick Koston <nick@koston.org>
7
8CVE: CVE-2024-23334
9
10Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/1c335944d6a8b1298baf179b7c0b3069f10c514b]
11
12Signed-off-by: Rahul Janani Pandi <RahulJanani.Pandi@windriver.com>
13---
14 CHANGES/8079.bugfix.rst | 1 +
15 aiohttp/web_urldispatcher.py | 18 +++++--
16 docs/web_advanced.rst | 16 ++++--
17 docs/web_reference.rst | 12 +++--
18 tests/test_web_urldispatcher.py | 91 +++++++++++++++++++++++++++++++++
19 5 files changed, 128 insertions(+), 10 deletions(-)
20 create mode 100644 CHANGES/8079.bugfix.rst
21
22diff --git a/CHANGES/8079.bugfix.rst b/CHANGES/8079.bugfix.rst
23new file mode 100644
24index 0000000..57bc8bf
25--- /dev/null
26+++ b/CHANGES/8079.bugfix.rst
27@@ -0,0 +1 @@
28+Improved validation of paths for static resources -- by :user:`bdraco`.
29diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py
30index 5942e35..e8a8023 100644
31--- a/aiohttp/web_urldispatcher.py
32+++ b/aiohttp/web_urldispatcher.py
33@@ -593,9 +593,14 @@ class StaticResource(PrefixResource):
34 url = url / filename
35
36 if append_version:
37+ unresolved_path = self._directory.joinpath(filename)
38 try:
39- filepath = self._directory.joinpath(filename).resolve()
40- if not self._follow_symlinks:
41+ if self._follow_symlinks:
42+ normalized_path = Path(os.path.normpath(unresolved_path))
43+ normalized_path.relative_to(self._directory)
44+ filepath = normalized_path.resolve()
45+ else:
46+ filepath = unresolved_path.resolve()
47 filepath.relative_to(self._directory)
48 except (ValueError, FileNotFoundError):
49 # ValueError for case when path point to symlink
50@@ -660,8 +665,13 @@ class StaticResource(PrefixResource):
51 # /static/\\machine_name\c$ or /static/D:\path
52 # where the static dir is totally different
53 raise HTTPForbidden()
54- filepath = self._directory.joinpath(filename).resolve()
55- if not self._follow_symlinks:
56+ unresolved_path = self._directory.joinpath(filename)
57+ if self._follow_symlinks:
58+ normalized_path = Path(os.path.normpath(unresolved_path))
59+ normalized_path.relative_to(self._directory)
60+ filepath = normalized_path.resolve()
61+ else:
62+ filepath = unresolved_path.resolve()
63 filepath.relative_to(self._directory)
64 except (ValueError, FileNotFoundError) as error:
65 # relatively safe
66diff --git a/docs/web_advanced.rst b/docs/web_advanced.rst
67index 3a98b78..5129397 100644
68--- a/docs/web_advanced.rst
69+++ b/docs/web_advanced.rst
70@@ -136,12 +136,22 @@ instead could be enabled with ``show_index`` parameter set to ``True``::
71
72 web.static('/prefix', path_to_static_folder, show_index=True)
73
74-When a symlink from the static directory is accessed, the server responses to
75-client with ``HTTP/404 Not Found`` by default. To allow the server to follow
76-symlinks, parameter ``follow_symlinks`` should be set to ``True``::
77+When a symlink that leads outside the static directory is accessed, the server
78+responds to the client with ``HTTP/404 Not Found`` by default. To allow the server to
79+follow symlinks that lead outside the static root, the parameter ``follow_symlinks``
80+should be set to ``True``::
81
82 web.static('/prefix', path_to_static_folder, follow_symlinks=True)
83
84+.. caution::
85+
86+ Enabling ``follow_symlinks`` can be a security risk, and may lead to
87+ a directory transversal attack. You do NOT need this option to follow symlinks
88+ which point to somewhere else within the static directory, this option is only
89+ used to break out of the security sandbox. Enabling this option is highly
90+ discouraged, and only expected to be used for edge cases in a local
91+ development setting where remote users do not have access to the server.
92+
93 When you want to enable cache busting,
94 parameter ``append_version`` can be set to ``True``
95
96diff --git a/docs/web_reference.rst b/docs/web_reference.rst
97index a156f47..b100676 100644
98--- a/docs/web_reference.rst
99+++ b/docs/web_reference.rst
100@@ -1836,9 +1836,15 @@ Router is any object that implements :class:`~aiohttp.abc.AbstractRouter` interf
101 by default it's not allowed and HTTP/403 will
102 be returned on directory access.
103
104- :param bool follow_symlinks: flag for allowing to follow symlinks from
105- a directory, by default it's not allowed and
106- HTTP/404 will be returned on access.
107+ :param bool follow_symlinks: flag for allowing to follow symlinks that lead
108+ outside the static root directory, by default it's not allowed and
109+ HTTP/404 will be returned on access. Enabling ``follow_symlinks``
110+ can be a security risk, and may lead to a directory transversal attack.
111+ You do NOT need this option to follow symlinks which point to somewhere
112+ else within the static directory, this option is only used to break out
113+ of the security sandbox. Enabling this option is highly discouraged,
114+ and only expected to be used for edge cases in a local development
115+ setting where remote users do not have access to the server.
116
117 :param bool append_version: flag for adding file version (hash)
118 to the url query string, this value will
119diff --git a/tests/test_web_urldispatcher.py b/tests/test_web_urldispatcher.py
120index f24f451..f40f6a5 100644
121--- a/tests/test_web_urldispatcher.py
122+++ b/tests/test_web_urldispatcher.py
123@@ -123,6 +123,97 @@ async def test_follow_symlink(tmp_dir_path, aiohttp_client) -> None:
124 assert (await r.text()) == data
125
126
127+async def test_follow_symlink_directory_traversal(
128+ tmp_path: pathlib.Path, aiohttp_client: AiohttpClient
129+) -> None:
130+ # Tests that follow_symlinks does not allow directory transversal
131+ data = "private"
132+
133+ private_file = tmp_path / "private_file"
134+ private_file.write_text(data)
135+
136+ safe_path = tmp_path / "safe_dir"
137+ safe_path.mkdir()
138+
139+ app = web.Application()
140+
141+ # Register global static route:
142+ app.router.add_static("/", str(safe_path), follow_symlinks=True)
143+ client = await aiohttp_client(app)
144+
145+ await client.start_server()
146+ # We need to use a raw socket to test this, as the client will normalize
147+ # the path before sending it to the server.
148+ reader, writer = await asyncio.open_connection(client.host, client.port)
149+ writer.write(b"GET /../private_file HTTP/1.1\r\n\r\n")
150+ response = await reader.readuntil(b"\r\n\r\n")
151+ assert b"404 Not Found" in response
152+ writer.close()
153+ await writer.wait_closed()
154+ await client.close()
155+
156+
157+async def test_follow_symlink_directory_traversal_after_normalization(
158+ tmp_path: pathlib.Path, aiohttp_client: AiohttpClient
159+) -> None:
160+ # Tests that follow_symlinks does not allow directory transversal
161+ # after normalization
162+ #
163+ # Directory structure
164+ # |-- secret_dir
165+ # | |-- private_file (should never be accessible)
166+ # | |-- symlink_target_dir
167+ # | |-- symlink_target_file (should be accessible via the my_symlink symlink)
168+ # | |-- sandbox_dir
169+ # | |-- my_symlink -> symlink_target_dir
170+ #
171+ secret_path = tmp_path / "secret_dir"
172+ secret_path.mkdir()
173+
174+ # This file is below the symlink target and should not be reachable
175+ private_file = secret_path / "private_file"
176+ private_file.write_text("private")
177+
178+ symlink_target_path = secret_path / "symlink_target_dir"
179+ symlink_target_path.mkdir()
180+
181+ sandbox_path = symlink_target_path / "sandbox_dir"
182+ sandbox_path.mkdir()
183+
184+ # This file should be reachable via the symlink
185+ symlink_target_file = symlink_target_path / "symlink_target_file"
186+ symlink_target_file.write_text("readable")
187+
188+ my_symlink_path = sandbox_path / "my_symlink"
189+ pathlib.Path(str(my_symlink_path)).symlink_to(str(symlink_target_path), True)
190+
191+ app = web.Application()
192+
193+ # Register global static route:
194+ app.router.add_static("/", str(sandbox_path), follow_symlinks=True)
195+ client = await aiohttp_client(app)
196+
197+ await client.start_server()
198+ # We need to use a raw socket to test this, as the client will normalize
199+ # the path before sending it to the server.
200+ reader, writer = await asyncio.open_connection(client.host, client.port)
201+ writer.write(b"GET /my_symlink/../private_file HTTP/1.1\r\n\r\n")
202+ response = await reader.readuntil(b"\r\n\r\n")
203+ assert b"404 Not Found" in response
204+ writer.close()
205+ await writer.wait_closed()
206+
207+ reader, writer = await asyncio.open_connection(client.host, client.port)
208+ writer.write(b"GET /my_symlink/symlink_target_file HTTP/1.1\r\n\r\n")
209+ response = await reader.readuntil(b"\r\n\r\n")
210+ assert b"200 OK" in response
211+ response = await reader.readuntil(b"readable")
212+ assert response == b"readable"
213+ writer.close()
214+ await writer.wait_closed()
215+ await client.close()
216+
217+
218 @pytest.mark.parametrize(
219 "dir_name,filename,data",
220 [
221--
2222.40.0
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp_3.8.6.bb b/meta-python/recipes-devtools/python/python3-aiohttp_3.8.6.bb
index f8ca9a4739..c805e17d86 100644
--- a/meta-python/recipes-devtools/python/python3-aiohttp_3.8.6.bb
+++ b/meta-python/recipes-devtools/python/python3-aiohttp_3.8.6.bb
@@ -4,6 +4,9 @@ HOMEPAGE = "https://github.com/aio-libs/aiohttp"
4LICENSE = "Apache-2.0" 4LICENSE = "Apache-2.0"
5LIC_FILES_CHKSUM = "file://LICENSE.txt;md5=748073912af33aa59430d3702aa32d41" 5LIC_FILES_CHKSUM = "file://LICENSE.txt;md5=748073912af33aa59430d3702aa32d41"
6 6
7SRC_URI += "file://CVE-2024-23334.patch \
8 "
9
7SRC_URI[sha256sum] = "b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c" 10SRC_URI[sha256sum] = "b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"
8 11
9PYPI_PACKAGE = "aiohttp" 12PYPI_PACKAGE = "aiohttp"