From a2200dc43d9fe0ee19b9185b30749c204a4dfd45 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 8 Nov 2023 19:25:05 +0000 Subject: [PATCH] Add HTTP method validation (#6533) (#7806) (cherry picked from commit 75fca0b00b4297d0a30c51ae97a65428336eb2c1) Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/pull/7806/commits/a43bc1779892e7014b7723c59d08fb37a000955e] CVE: CVE-2023-49082 Co-authored-by: Andrew Svetlov Signed-off-by: Jiaying Song --- CHANGES/6533.feature | 1 + aiohttp/client_reqrep.py | 9 ++++++++- tests/test_client_request.py | 5 +++++ tests/test_web_request.py | 9 +++++++-- 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 CHANGES/6533.feature diff --git a/CHANGES/6533.feature b/CHANGES/6533.feature new file mode 100644 index 0000000..36bcbeb --- /dev/null +++ b/CHANGES/6533.feature @@ -0,0 +1 @@ +Add HTTP method validation. diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index d3cd77e..a8135b2 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -78,6 +78,7 @@ if TYPE_CHECKING: # pragma: no cover from .tracing import Trace +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") json_re = re.compile(r"^application/(?:[\w.+-]+?\+)?json") @@ -266,10 +267,16 @@ class ClientRequest: proxy_headers: Optional[LooseHeaders] = None, traces: Optional[List["Trace"]] = None, ): - if loop is None: loop = asyncio.get_event_loop() + match = _CONTAINS_CONTROL_CHAR_RE.search(method) + if match: + raise ValueError( + f"Method cannot contain non-token characters {method!r} " + "(found at least {match.group()!r})" + ) + assert isinstance(url, URL), url assert isinstance(proxy, (URL, type(None))), proxy # FIXME: session is None in tests only, need to fix tests diff --git a/tests/test_client_request.py b/tests/test_client_request.py index 009f1a0..d0f208b 100644 --- a/tests/test_client_request.py +++ b/tests/test_client_request.py @@ -89,6 +89,11 @@ def test_method3(make_request) -> None: assert req.method == "HEAD" +def test_method_invalid(make_request) -> None: + with pytest.raises(ValueError, match="Method cannot contain non-token characters"): + make_request("METHOD WITH\nWHITESPACES", "http://python.org/") + + def test_version_1_0(make_request) -> None: req = make_request("get", "http://python.org/", version="1.0") assert req.version == (1, 0) diff --git a/tests/test_web_request.py b/tests/test_web_request.py index c6aeaf8..2bb0cd5 100644 --- a/tests/test_web_request.py +++ b/tests/test_web_request.py @@ -43,7 +43,10 @@ def test_base_ctor() -> None: assert "GET" == req.method assert HttpVersion(1, 1) == req.version - assert req.host == socket.getfqdn() + # MacOS may return CamelCased host name, need .lower() + # FQDN can be wider than host, e.g. + # 'fv-az397-495' in 'fv-az397-495.internal.cloudapp.net' + assert req.host.lower() in socket.getfqdn().lower() assert "/path/to?a=1&b=2" == req.path_qs assert "/path/to" == req.path assert "a=1&b=2" == req.query_string @@ -66,7 +69,9 @@ def test_ctor() -> None: assert "GET" == req.method assert HttpVersion(1, 1) == req.version # MacOS may return CamelCased host name, need .lower() - assert req.host.lower() == socket.getfqdn().lower() + # FQDN can be wider than host, e.g. + # 'fv-az397-495' in 'fv-az397-495.internal.cloudapp.net' + assert req.host.lower() in socket.getfqdn().lower() assert "/path/to?a=1&b=2" == req.path_qs assert "/path/to" == req.path assert "a=1&b=2" == req.query_string -- 2.25.1