From: Stefano Rivera <stefanor@debian.org>
Date: Fri, 26 Feb 2021 15:41:09 -0800
Subject: CVE-2021-21330: Prevent open redirects
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 8bit

in the ``aiohttp.web.normalize_path_middleware`` middleware.

Thanks to `Beast Glatisant <https://github.com/g147>`__ for
finding the firstinstance of this issue and `Jelmer Vernooĳ
<https://jelmer.uk/>`__ for reporting and tracking it down
in aiohttp.

Bug-Upstream: https://github.com/aio-libs/aiohttp/security/advisories/GHSA-v6wp-4m6f-gcjg
---
 aiohttp/web_middlewares.py   |  1 +
 tests/test_web_middleware.py | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/aiohttp/web_middlewares.py b/aiohttp/web_middlewares.py
index 39bfab3..43c6144 100644
--- a/aiohttp/web_middlewares.py
+++ b/aiohttp/web_middlewares.py
@@ -102,6 +102,7 @@ def normalize_path_middleware(
                 paths_to_check.append(merged_slashes[:-1])
 
             for path in paths_to_check:
+                path = re.sub("^//+", "/", path)  # SECURITY: GHSA-v6wp-4m6f-gcjg
                 resolves, request = await _check_request_resolves(
                     request, path)
                 if resolves:
diff --git a/tests/test_web_middleware.py b/tests/test_web_middleware.py
index 2eba995..f0107c6 100644
--- a/tests/test_web_middleware.py
+++ b/tests/test_web_middleware.py
@@ -361,6 +361,38 @@ async def test_mixed_middleware(loop, aiohttp_client) -> None:
     assert re.match(p2, str(w.list[0].message))
     assert re.match(p1, str(w.list[1].message))
 
+    @pytest.mark.parametrize(
+        ["append_slash", "remove_slash"],
+        [
+            (True, False),
+            (False, True),
+            (False, False),
+        ],
+    )
+    async def test_open_redirects(
+        self, append_slash, remove_slash, aiohttp_client
+    ) -> None:
+        async def handle(request: web.Request) -> web.StreamResponse:
+            pytest.fail(
+                msg="Security advisory 'GHSA-v6wp-4m6f-gcjg' test handler "
+                "matched unexpectedly",
+                pytrace=False,
+            )
+
+        app = web.Application(
+            middlewares=[
+                web.normalize_path_middleware(
+                    append_slash=append_slash, remove_slash=remove_slash
+                )
+            ]
+        )
+        app.add_routes([web.get("/", handle), web.get("/google.com", handle)])
+        client = await aiohttp_client(app, server_kwargs={"skip_url_asserts": True})
+        resp = await client.get("//google.com", allow_redirects=False)
+        assert resp.status == 308
+        assert resp.headers["Location"] == "/google.com"
+        assert resp.url.query == URL("//google.com").query
+
 
 async def test_old_style_middleware_class(loop, aiohttp_client) -> None:
     async def handler(request):
