diff --git a/CVE-2023-47641.patch b/CVE-2023-47641.patch new file mode 100644 index 0000000000000000000000000000000000000000..2afc5b95010e03359029df769bcb7b52cc44cd6d --- /dev/null +++ b/CVE-2023-47641.patch @@ -0,0 +1,77 @@ +From f016f0680e4ace6742b03a70cb0382ce86abe371 Mon Sep 17 00:00:00 2001 +From: Andrew Svetlov +Date: Sun, 31 Oct 2021 19:03:06 +0200 +Subject: [PATCH] Raise '400: Content-Length can't be present with + Transfer-Encoding' if both Content-Length and Transfer-Encoding are sent by + peer (#6182) + +--- + CHANGES/6182.bugfix | 1 + + aiohttp/http_parser.py | 12 ++++++++++-- + tests/test_http_parser.py | 15 ++++++++++++++- + 3 files changed, 25 insertions(+), 3 deletions(-) + create mode 100644 CHANGES/6182.bugfix + +diff --git a/CHANGES/6182.bugfix b/CHANGES/6182.bugfix +new file mode 100644 +index 0000000000..28daaa328a +--- /dev/null ++++ b/CHANGES/6182.bugfix +@@ -0,0 +1 @@ ++Raise ``400: Content-Length can't be present with Transfer-Encoding`` if both ``Content-Length`` and ``Transfer-Encoding`` are sent by peer by both C and Python implementations +diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py +index 4a4ae31ae6..e1b86e8e4f 100644 +--- a/aiohttp/http_parser.py ++++ b/aiohttp/http_parser.py +@@ -28,6 +28,7 @@ + from .base_protocol import BaseProtocol + from .helpers import NO_EXTENSIONS, BaseTimerContext + from .http_exceptions import ( ++ BadHttpMessage, + BadStatusLine, + ContentEncodingError, + ContentLengthError, +@@ -489,8 +490,15 @@ def parse_headers( + + # chunking + te = headers.get(hdrs.TRANSFER_ENCODING) +- if te and "chunked" in te.lower(): +- chunked = True ++ if te is not None: ++ te_lower = te.lower() ++ if "chunked" in te_lower: ++ chunked = True ++ ++ if hdrs.CONTENT_LENGTH in headers: ++ raise BadHttpMessage( ++ "Content-Length can't be present with Transfer-Encoding", ++ ) + + return (headers, raw_headers, close_conn, encoding, upgrade, chunked) + +diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py +index 78e9ea6401..d86d238f58 100644 +--- a/tests/test_http_parser.py ++++ b/tests/test_http_parser.py +@@ -291,7 +291,20 @@ def test_request_chunked(parser) -> None: + assert isinstance(payload, streams.StreamReader) + + +-def test_conn_upgrade(parser) -> None: ++def test_request_te_chunked_with_content_length(parser: Any) -> None: ++ text = ( ++ b"GET /test HTTP/1.1\r\n" ++ b"content-length: 1234\r\n" ++ b"transfer-encoding: chunked\r\n\r\n" ++ ) ++ with pytest.raises( ++ http_exceptions.BadHttpMessage, ++ match="Content-Length can't be present with Transfer-Encoding", ++ ): ++ parser.feed_data(text) ++ ++ ++def test_conn_upgrade(parser: Any) -> None: + text = ( + b"GET /test HTTP/1.1\r\n" + b"connection: upgrade\r\n" diff --git a/CVE-2023-49081.patch b/CVE-2023-49081.patch new file mode 100644 index 0000000000000000000000000000000000000000..5294e21a98539d37c7d81d31ae4be0e2bb4bd5b0 --- /dev/null +++ b/CVE-2023-49081.patch @@ -0,0 +1,91 @@ +From 53476dfd4ef4fb1bb74a267714bbc39eda71b403 Mon Sep 17 00:00:00 2001 +From: Sam Bull +Date: Mon, 13 Nov 2023 22:36:04 +0000 +Subject: [PATCH] Disallow arbitrary sequence types in version (#7835) (#7836) + +Origin: https://github.com/aio-libs/aiohttp/commit/53476dfd4ef4fb1bb74a267714bbc39eda71b403 + +(cherry picked from commit 1e86b777e61cf4eefc7d92fa57fa19dcc676013b) +--- + CHANGES/7835.bugfix | 1 + + aiohttp/client_reqrep.py | 4 ++-- + tests/test_client_request.py | 20 +++++++++++++++++--- + 3 files changed, 20 insertions(+), 5 deletions(-) + create mode 100644 CHANGES/7835.bugfix + +diff --git a/CHANGES/7835.bugfix b/CHANGES/7835.bugfix +new file mode 100644 +index 0000000000..4ce3af4f6f +--- /dev/null ++++ b/CHANGES/7835.bugfix +@@ -0,0 +1 @@ ++Fixed arbitrary sequence types being allowed to inject headers via version parameter -- by :user:`Dreamsorcerer` +diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py +index 851ab220b8..4cea7466d8 100644 +--- a/aiohttp/client_reqrep.py ++++ b/aiohttp/client_reqrep.py +@@ -706,8 +706,8 @@ async def send(self, conn: "Connection") -> "ClientResponse": + self.headers[hdrs.CONNECTION] = connection + + # status + headers +- status_line = "{0} {1} HTTP/{2[0]}.{2[1]}".format( +- self.method, path, self.version ++ status_line = "{0} {1} HTTP/{v.major}.{v.minor}".format( ++ self.method, path, v=self.version + ) + await writer.write_headers(status_line, self.headers) + +diff --git a/tests/test_client_request.py b/tests/test_client_request.py +index 0f58d752de..c8ce98d403 100644 +--- a/tests/test_client_request.py ++++ b/tests/test_client_request.py +@@ -21,6 +21,7 @@ + Fingerprint, + _merge_ssl_params, + ) ++from aiohttp.http import HttpVersion + from aiohttp.test_utils import make_mocked_coro + + +@@ -623,18 +624,18 @@ async def test_connection_header(loop, conn) -> None: + req.headers.clear() + + req.keep_alive.return_value = True +- req.version = (1, 1) ++ req.version = HttpVersion(1, 1) + req.headers.clear() + await req.send(conn) + assert req.headers.get("CONNECTION") is None + +- req.version = (1, 0) ++ req.version = HttpVersion(1, 0) + req.headers.clear() + await req.send(conn) + assert req.headers.get("CONNECTION") == "keep-alive" + + req.keep_alive.return_value = False +- req.version = (1, 1) ++ req.version = HttpVersion(1, 1) + req.headers.clear() + await req.send(conn) + assert req.headers.get("CONNECTION") == "close" +@@ -1161,6 +1162,19 @@ async def gen(): + resp.close() + + ++async def test_bad_version(loop, conn) -> None: ++ req = ClientRequest( ++ "GET", ++ URL("http://python.org"), ++ loop=loop, ++ headers={"Connection": "Close"}, ++ version=("1", "1\r\nInjected-Header: not allowed"), ++ ) ++ ++ with pytest.raises(AttributeError): ++ await req.send(conn) ++ ++ + async def test_custom_response_class(loop, conn) -> None: + class CustomResponse(ClientResponse): + def read(self, decode=False): diff --git a/CVE-2024-27306.patch b/CVE-2024-27306.patch new file mode 100644 index 0000000000000000000000000000000000000000..3f3781674c412380a5d8e371420301ff680fa0cb --- /dev/null +++ b/CVE-2024-27306.patch @@ -0,0 +1,244 @@ +From 28335525d1eac015a7e7584137678cbb6ff19397 Mon Sep 17 00:00:00 2001 +From: Sam Bull +Date: Thu, 24 Oct 2024 03:58:45 +0800 +Subject: [PATCH] Escape filenames and paths in HTML when generating index + pages (#8317) (#8319) + +Co-authored-by: J. Nick Koston +(cherry picked from commit ffbc43233209df302863712b511a11bdb6001b0f) + +--- + CHANGES/8317.bugfix.rst | 1 + + aiohttp/web_urldispatcher.py | 13 ++-- + tests/test_web_urldispatcher.py | 125 ++++++++++++++++++++++++++++---- + 3 files changed, 119 insertions(+), 20 deletions(-) + create mode 100644 CHANGES/8317.bugfix.rst + +diff --git a/CHANGES/8317.bugfix.rst b/CHANGES/8317.bugfix.rst +new file mode 100644 +index 0000000..b24ef2a +--- /dev/null ++++ b/CHANGES/8317.bugfix.rst +@@ -0,0 +1 @@ ++Escaped filenames in static view -- by :user:`bdraco`. +diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py +index 2afd72f..c8e2836 100644 +--- a/aiohttp/web_urldispatcher.py ++++ b/aiohttp/web_urldispatcher.py +@@ -1,7 +1,9 @@ + import abc + import asyncio + import base64 ++import functools + import hashlib ++import html + import inspect + import keyword + import os +@@ -85,6 +87,8 @@ _WebHandler = Callable[[Request], Awaitable[StreamResponse]] + _ExpectHandler = Callable[[Request], Awaitable[None]] + _Resolve = Tuple[Optional[AbstractMatchInfo], Set[str]] + ++html_escape = functools.partial(html.escape, quote=True) ++ + + class _InfoDict(TypedDict, total=False): + path: str +@@ -692,7 +696,7 @@ class StaticResource(PrefixResource): + assert filepath.is_dir() + + relative_path_to_dir = filepath.relative_to(self._directory).as_posix() +- index_of = f"Index of /{relative_path_to_dir}" ++ index_of = f"Index of /{html_escape(relative_path_to_dir)}" + h1 = f"

{index_of}

" + + index_list = [] +@@ -700,7 +704,8 @@ class StaticResource(PrefixResource): + for _file in sorted(dir_index): + # show file url as relative to static path + rel_path = _file.relative_to(self._directory).as_posix() +- file_url = self._prefix + "/" + rel_path ++ quoted_file_url = _quote_path(f"{self._prefix}/{rel_path}") ++ + + # if file is a directory, add '/' to the end of the name + if _file.is_dir(): +@@ -709,9 +714,7 @@ class StaticResource(PrefixResource): + file_name = _file.name + + index_list.append( +- '
  • {name}
  • '.format( +- url=file_url, name=file_name +- ) ++ f'
  • {html_escape(file_name)}
  • ' + ) + ul = "
      \n{}\n
    ".format("\n".join(index_list)) + body = f"\n{h1}\n{ul}\n" +diff --git a/tests/test_web_urldispatcher.py b/tests/test_web_urldispatcher.py +index 0ba2e7c..cc1ea75 100644 +--- a/tests/test_web_urldispatcher.py ++++ b/tests/test_web_urldispatcher.py +@@ -2,6 +2,7 @@ import asyncio + import functools + import os + import pathlib ++import sys + import shutil + import sys + import tempfile +@@ -32,35 +33,42 @@ def tmp_dir_path(request): + + + @pytest.mark.parametrize( +- "show_index,status,prefix,data", ++ "show_index,status,prefix,request_path,data", + [ +- pytest.param(False, 403, "/", None, id="index_forbidden"), ++ pytest.param(False, 403, "/", "/", None, id="index_forbidden"), + pytest.param( + True, + 200, + "/", +- b"\n\nIndex of /.\n" +- b"\n\n

    Index of /.

    \n\n\n", +- id="index_root", ++ "/", ++ b"\n\nIndex of /.\n\n\n

    Index of" ++ b' /.

    \n\n\n", + ), + pytest.param( + True, + 200, + "/static", +- b"\n\nIndex of /.\n" +- b"\n\n

    Index of /.

    \n\n\n", ++ "/static", ++ b"\n\nIndex of /.\n\n\n

    Index of" ++ b' /.

    \n\n\n', + id="index_static", + ), ++ pytest.param( ++ True, ++ 200, ++ "/static", ++ "/static/my_dir", ++ b"\n\nIndex of /my_dir\n\n\n

    " ++ b'Index of /my_dir

    \n\n\n", ++ id="index_subdir", ++ ), + ], + ) + async def test_access_root_of_static_handler( +- tmp_dir_path, aiohttp_client, show_index, status, prefix, data ++ tmp_dir_path, aiohttp_client, show_index, status, prefix, request_path,data + ) -> None: + # Tests the operation of static file server. + # Try to access the root of static file server, and make +@@ -85,7 +93,94 @@ async def test_access_root_of_static_handler( + client = await aiohttp_client(app) + + # Request the root of the static directory. +- r = await client.get(prefix) ++ async with await client.get(request_path) as r: ++ assert r.status == status ++ ++ if data: ++ assert r.headers["Content-Type"] == "text/html; charset=utf-8" ++ read_ = await r.read() ++ assert read_ == data ++ ++ ++@pytest.mark.internal # Dependent on filesystem ++@pytest.mark.skipif( ++ not sys.platform.startswith("linux"), ++ reason="Invalid filenames on some filesystems (like Windows)", ++) ++@pytest.mark.parametrize( ++ "show_index,status,prefix,request_path,data", ++ [ ++ pytest.param(False, 403, "/", "/", None, id="index_forbidden"), ++ pytest.param( ++ True, ++ 200, ++ "/", ++ "/", ++ b"\n\nIndex of /.\n\n\n

    Index of" ++ b' /.

    \n\n\n", ++ ), ++ pytest.param( ++ True, ++ 200, ++ "/static", ++ "/static", ++ b"\n\nIndex of /.\n\n\n

    Index of" ++ b' /.

    \n\n\n", ++ id="index_static", ++ ), ++ pytest.param( ++ True, ++ 200, ++ "/static", ++ "/static/.dir", ++ b"\n\nIndex of /<img src=0 onerror=alert(1)>.dir</t" ++ b"itle>\n</head>\n<body>\n<h1>Index of /<img src=0 onerror=alert(1)>.di" ++ b'r</h1>\n<ul>\n<li><a href="/static/%3Cimg%20src=0%20onerror=alert(1)%3E.di' ++ b'r/my_file_in_dir">my_file_in_dir</a></li>\n</ul>\n</body>\n</html>', ++ id="index_subdir", ++ ), ++ ], ++) ++async def test_access_root_of_static_handler_xss( ++ tmp_path: pathlib.Path, ++ aiohttp_client: AiohttpClient, ++ show_index: bool, ++ status: int, ++ prefix: str, ++ request_path: str, ++ data: Optional[bytes], ++) -> None: ++ # Tests the operation of static file server. ++ # Try to access the root of static file server, and make ++ # sure that correct HTTP statuses are returned depending if we directory ++ # index should be shown or not. ++ # Ensure that html in file names is escaped. ++ # Ensure that links are url quoted. ++ my_file = tmp_path / "<img src=0 onerror=alert(1)>.txt" ++ my_dir = tmp_path / "<img src=0 onerror=alert(1)>.dir" ++ my_dir.mkdir() ++ my_file_in_dir = my_dir / "my_file_in_dir" ++ ++ with my_file.open("w") as fw: ++ fw.write("hello") ++ ++ with my_file_in_dir.open("w") as fw: ++ fw.write("world") ++ ++ app = web.Application() ++ ++ # Register global static route: ++ app.router.add_static(prefix, str(tmp_path), show_index=show_index) ++ client = await aiohttp_client(app) ++ ++ # Request the root of the static directory. ++ async with await client.get(request_path) as r: + assert r.status == status + + if data: +-- +2.43.0 + diff --git a/change-require-chardet-package-version.patch b/change-require-chardet-package-version.patch new file mode 100644 index 0000000000000000000000000000000000000000..8441513647a265bda243d617e0570e77a384f215 --- /dev/null +++ b/change-require-chardet-package-version.patch @@ -0,0 +1,22 @@ +diff -Nur a/aiohttp.egg-info/requires.txt b/aiohttp.egg-info/requires.txt +--- a/aiohttp.egg-info/requires.txt 2021-03-07 04:59:20.000000000 +0800 ++++ b/aiohttp.egg-info/requires.txt 2022-11-07 15:55:07.214419948 +0800 +@@ -1,5 +1,5 @@ + attrs>=17.3.0 +-chardet<5.0,>=2.0 ++chardet<=5.0,>=2.0 + multidict<7.0,>=4.5 + async_timeout<4.0,>=3.0 + yarl<2.0,>=1.0 +diff -Nur a/setup.py b/setup.py +--- a/setup.py 2021-03-07 04:59:13.000000000 +0800 ++++ b/setup.py 2022-11-07 15:55:46.671059857 +0800 +@@ -66,7 +66,7 @@ + + install_requires = [ + "attrs>=17.3.0", +- "chardet>=2.0,<5.0", ++ "chardet>=2.0,<=5.0", + "multidict>=4.5,<7.0", + "async_timeout>=3.0,<4.0", + "yarl>=1.0,<2.0", diff --git a/python-aiohttp.spec b/python-aiohttp.spec index a7eff126dd0123d60ff907a5519838853ca64f0c..847fbaf15876f6c59d05348cd212bc3d322e8abc 100644 --- a/python-aiohttp.spec +++ b/python-aiohttp.spec @@ -1,11 +1,15 @@ %global _empty_manifest_terminate_build 0 Name: python-aiohttp Version: 3.7.4 -Release: 1 +Release: 5 Summary: Async http client/server framework (asyncio) License: Apache 2 URL: https://github.com/aio-libs/aiohttp Source0: https://files.pythonhosted.org/packages/99/f5/90ede947a3ce2d6de1614799f5fea4e93c19b6520a59dc5d2f64123b032f/aiohttp-3.7.4.post0.tar.gz +Patch0: change-require-chardet-package-version.patch +Patch1: CVE-2023-47641.patch +Patch2: CVE-2023-49081.patch +Patch3: CVE-2024-27306.patch BuildRequires: python3-attrs BuildRequires: python3-chardet @@ -36,7 +40,7 @@ Provides: python3-aiohttp-doc Development documents and examples for aiohttp. %prep -%autosetup -n aiohttp-3.7.4.post0 +%autosetup -n aiohttp-3.7.4.post0 -p1 %build %py3_build @@ -76,5 +80,17 @@ mv %{buildroot}/doclist.lst . %{_docdir}/* %changelog +* Tue Nov 07 2024 changtao <changtao@kylinos.cn> - 3.7.4-5 +- Fix CVE-2024-27306.patch + +* Fri Dec 01 2023 wangkai <13474090681@163.com> - 3.7.4-4 +- Fix CVE-2023-49081 + +* Thu Nov 16 2023 yaoxin <yao_xin001@hoperun.com> - 3.7.4-3 +- Fix CVE-2023-47641 + +* Mon Nov 7 2022 liyanan <liyanan32@h-partners.com> - 3.7.4-2 +- change chardet version to fix installed error + * Fri Jul 23 2021 wutao <wutao61@huawei.com> - 3.7.4-1 - Package init