Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Leap:15.5:Update
python-aiohttp.35324
CVE-2024-42367-path-traversal-via-symlink.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File CVE-2024-42367-path-traversal-via-symlink.patch of Package python-aiohttp.35324
From f98240ad2279c3e97b65eddce40d37948f383416 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" <nick@koston.org> Date: Thu, 8 Aug 2024 11:19:28 -0500 Subject: [PATCH] Do not follow symlinks for compressed file variants (#8652) Co-authored-by: Steve Repsher <steverep@users.noreply.github.com> (cherry picked from commit b0536ae6babf160105d4025ea87c02b9fa5629f1) --- CHANGES/8652.bugfix.rst | 1 + aiohttp/web_fileresponse.py | 5 ++++- tests/test_web_sendfile.py | 14 +++++++------- tests/test_web_urldispatcher.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 CHANGES/8652.bugfix.rst Index: aiohttp-3.9.3/CHANGES/8652.bugfix.rst =================================================================== --- /dev/null +++ aiohttp-3.9.3/CHANGES/8652.bugfix.rst @@ -0,0 +1 @@ +Fixed incorrectly following symlinks for compressed file variants -- by :user:`steverep`. Index: aiohttp-3.9.3/tests/test_web_sendfile.py =================================================================== --- aiohttp-3.9.3.orig/tests/test_web_sendfile.py +++ aiohttp-3.9.3/tests/test_web_sendfile.py @@ -1,10 +1,13 @@ from pathlib import Path +from stat import S_IFREG, S_IRUSR, S_IWUSR from unittest import mock from aiohttp import hdrs from aiohttp.test_utils import make_mocked_coro, make_mocked_request from aiohttp.web_fileresponse import FileResponse +MOCK_MODE = S_IFREG | S_IRUSR | S_IWUSR + def test_using_gzip_if_header_present_and_file_available(loop) -> None: request = make_mocked_request( @@ -12,8 +15,9 @@ def test_using_gzip_if_header_present_an ) gz_filepath = mock.create_autospec(Path, spec_set=True) - gz_filepath.stat.return_value.st_size = 1024 - gz_filepath.stat.return_value.st_mtime_ns = 1603733507222449291 + gz_filepath.lstat.return_value.st_size = 1024 + gz_filepath.lstat.return_value.st_mtime_ns = 1603733507222449291 + gz_filepath.lstat.return_value.st_mode = MOCK_MODE filepath = mock.create_autospec(Path, spec_set=True) filepath.name = "logo.png" @@ -33,14 +37,16 @@ def test_gzip_if_header_not_present_and_ request = make_mocked_request("GET", "http://python.org/logo.png", headers={}) gz_filepath = mock.create_autospec(Path, spec_set=True) - gz_filepath.stat.return_value.st_size = 1024 - gz_filepath.stat.return_value.st_mtime_ns = 1603733507222449291 + gz_filepath.lstat.return_value.st_size = 1024 + gz_filepath.lstat.return_value.st_mtime_ns = 1603733507222449291 + gz_filepath.lstat.return_value.st_mode = MOCK_MODE filepath = mock.create_autospec(Path, spec_set=True) filepath.name = "logo.png" filepath.with_name.return_value = gz_filepath - filepath.stat.return_value.st_size = 1024 - filepath.stat.return_value.st_mtime_ns = 1603733507222449291 + filepath.lstat.return_value.st_size = 1024 + filepath.lstat.return_value.st_mtime_ns = 1603733507222449291 + filepath.lstat.return_value.st_mode = MOCK_MODE file_sender = FileResponse(filepath) file_sender._path = filepath @@ -56,13 +62,14 @@ def test_gzip_if_header_not_present_and_ request = make_mocked_request("GET", "http://python.org/logo.png", headers={}) gz_filepath = mock.create_autospec(Path, spec_set=True) - gz_filepath.stat.side_effect = OSError(2, "No such file or directory") + gz_filepath.lstat.side_effect = OSError(2, "No such file or directory") filepath = mock.create_autospec(Path, spec_set=True) filepath.name = "logo.png" filepath.with_name.return_value = gz_filepath - filepath.stat.return_value.st_size = 1024 - filepath.stat.return_value.st_mtime_ns = 1603733507222449291 + filepath.lstat.return_value.st_size = 1024 + filepath.lstat.return_value.st_mtime_ns = 1603733507222449291 + filepath.lstat.return_value.st_mode = MOCK_MODE file_sender = FileResponse(filepath) file_sender._path = filepath @@ -80,13 +87,14 @@ def test_gzip_if_header_present_and_file ) gz_filepath = mock.create_autospec(Path, spec_set=True) - gz_filepath.stat.side_effect = OSError(2, "No such file or directory") + gz_filepath.lstat.side_effect = OSError(2, "No such file or directory") filepath = mock.create_autospec(Path, spec_set=True) filepath.name = "logo.png" filepath.with_name.return_value = gz_filepath - filepath.stat.return_value.st_size = 1024 - filepath.stat.return_value.st_mtime_ns = 1603733507222449291 + filepath.lstat.return_value.st_size = 1024 + filepath.lstat.return_value.st_mtime_ns = 1603733507222449291 + filepath.lstat.return_value.st_mode = MOCK_MODE file_sender = FileResponse(filepath) file_sender._path = filepath @@ -103,8 +111,9 @@ def test_status_controlled_by_user(loop) filepath = mock.create_autospec(Path, spec_set=True) filepath.name = "logo.png" - filepath.stat.return_value.st_size = 1024 - filepath.stat.return_value.st_mtime_ns = 1603733507222449291 + filepath.lstat.return_value.st_size = 1024 + filepath.lstat.return_value.st_mtime_ns = 1603733507222449291 + filepath.lstat.return_value.st_mode = MOCK_MODE file_sender = FileResponse(filepath, status=203) file_sender._path = filepath Index: aiohttp-3.9.3/tests/test_web_urldispatcher.py =================================================================== --- aiohttp-3.9.3.orig/tests/test_web_urldispatcher.py +++ aiohttp-3.9.3/tests/test_web_urldispatcher.py @@ -439,6 +439,38 @@ async def test_access_symlink_loop( assert r.status == 404 +async def test_access_compressed_file_as_symlink( + tmp_path: pathlib.Path, aiohttp_client: AiohttpClient +) -> None: + """Test that compressed file variants as symlinks are ignored.""" + private_file = tmp_path / "private.txt" + private_file.write_text("private info") + www_dir = tmp_path / "www" + www_dir.mkdir() + gz_link = www_dir / "file.txt.gz" + gz_link.symlink_to(f"../{private_file.name}") + + app = web.Application() + app.router.add_static("/", www_dir) + client = await aiohttp_client(app) + + # Symlink should be ignored; response reflects missing uncompressed file. + resp = await client.get(f"/{gz_link.stem}", auto_decompress=False) + assert resp.status == 404 + resp.release() + + # Again symlin is ignored, and then uncompressed is served. + txt_file = gz_link.with_suffix("") + txt_file.write_text("public data") + resp = await client.get(f"/{txt_file.name}") + assert resp.status == 200 + assert resp.headers.get("Content-Encoding") is None + assert resp.content_type == "text/plain" + assert await resp.text() == "public data" + resp.release() + await client.close() + + async def test_access_special_resource( tmp_path: pathlib.Path, aiohttp_client: AiohttpClient ) -> None: Index: aiohttp-3.9.3/aiohttp/web_fileresponse.py =================================================================== --- aiohttp-3.9.3.orig/aiohttp/web_fileresponse.py +++ aiohttp-3.9.3/aiohttp/web_fileresponse.py @@ -2,6 +2,7 @@ import asyncio import mimetypes import os import pathlib +from stat import S_ISREG from typing import ( # noqa IO, TYPE_CHECKING, @@ -136,12 +137,16 @@ class FileResponse(StreamResponse): if check_for_gzipped_file: gzip_path = filepath.with_name(filepath.name + ".gz") try: - return gzip_path, gzip_path.stat(), True + st = gzip_path.lstat() + if S_ISREG(st.st_mode): + return gzip_path, st, True except OSError: # Fall through and try the non-gzipped file pass - return filepath, filepath.stat(), False + st = filepath.lstat() + if S_ISREG(st.st_mode): + return filepath, st, False async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]: loop = asyncio.get_event_loop()
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor