summaryrefslogtreecommitdiffstats
path: root/meta-python/recipes-devtools/python/python3-aiohttp
diff options
context:
space:
mode:
authorRahul Janani Pandi <RahulJanani.Pandi@windriver.com>2024-04-25 07:10:52 +0000
committerArmin Kuster <akuster808@gmail.com>2024-04-28 13:10:23 -0400
commita9a49989479b47c5f1158023f1040e4ad7b7b4a2 (patch)
tree6748efb484fa7bfa5ea8dbcf8ea069241365769b /meta-python/recipes-devtools/python/python3-aiohttp
parentfdc54c502951bde8ad91aeaa9678813b25ff54e7 (diff)
downloadmeta-openembedded-a9a49989479b47c5f1158023f1040e4ad7b7b4a2.tar.gz
python3-aiohttp: Fix CVE-2024-23334
aiohttp is an asynchronous HTTP client/server framework for asyncio and Python.When using aiohttp as a web server and configuring static routes, it is necessary to specify the root path for static files. Additionally, the option 'follow_symlinks' can be used to determine whether to follow symbolic links outside the static root directory. When 'follow_symlinks' is set to True, there is no validation to check if reading a file is within the root directory. This can lead to directory traversal vulnerabilities, resulting in unauthorized access to arbitrary files on the system, even when symlinks are not present. Disabling follow_symlinks and using a reverse proxy are encouraged mitigations. Version 3.9.2 fixes this issue. References: https://security-tracker.debian.org/tracker/CVE-2024-23334 https://github.com/aio-libs/aiohttp/releases/tag/v3.9.2 Signed-off-by: Rahul Janani Pandi <RahulJanani.Pandi@windriver.com> Signed-off-by: Armin Kuster <akuster808@gmail.com>
Diffstat (limited to 'meta-python/recipes-devtools/python/python3-aiohttp')
-rw-r--r--meta-python/recipes-devtools/python/python3-aiohttp/CVE-2024-23334.patch222
1 files changed, 222 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