mirror of
https://git.yoctoproject.org/poky
synced 2026-07-31 10:17:45 +02:00
python3-urllib3: fix CVE-2026-44431
Applies the upstream fix [1] referenced in [2] and addresses the
sensitive-header redirect handling issue in proxied low-level urllib3 requests.
[1] 5ec0de499b
[2] https://ubuntu.com/security/CVE-2026-44431
References:
https://nvd.nist.gov/vuln/detail/CVE-2026-44431
(From OE-Core rev: a7f0523f5ff7a8fef611d30ad41b12d73eecec8c)
Signed-off-by: Sudhir Dumbhare <sudumbha@cisco.com>
Signed-off-by: Yoann Congal <yoann.congal@smile.fr>
Signed-off-by: Paul Barker <paul@pbarker.dev>
This commit is contained in:
committed by
Paul Barker
parent
3d80d019e6
commit
a184998193
@@ -0,0 +1,163 @@
|
||||
From d5517b0ab50030a8f389757b3ba648633c504cbc Mon Sep 17 00:00:00 2001
|
||||
From: Illia Volochii <illia.volochii@gmail.com>
|
||||
Date: Thu, 7 May 2026 18:40:31 +0300
|
||||
Subject: [PATCH] Merge commit from fork
|
||||
|
||||
* Remove sensitive headers in proxy pools too
|
||||
|
||||
* Add a changelog entry
|
||||
|
||||
* Check retries history in tests
|
||||
|
||||
CVE: CVE-2026-44431
|
||||
Upstream-Status: Backport [https://github.com/urllib3/urllib3/commit/5ec0de499b9166ca71c65ab04f2a7e4eb0d66fcc]
|
||||
|
||||
Co-authored-by: Copilot <copilot@github.com>
|
||||
|
||||
---------
|
||||
|
||||
Co-authored-by: Copilot <copilot@github.com>
|
||||
(cherry picked from commit 5ec0de499b9166ca71c65ab04f2a7e4eb0d66fcc)
|
||||
Signed-off-by: Sudhir Dumbhare <sudumbha@cisco.com>
|
||||
---
|
||||
changelog/GHSA-qccp-gfcp-xxvc.bugfix.rst | 3 +
|
||||
dummyserver/asgi_proxy.py | 1 +
|
||||
src/urllib3/connectionpool.py | 12 ++++
|
||||
.../test_proxy_poolmanager.py | 72 +++++++++++++++++++
|
||||
4 files changed, 88 insertions(+)
|
||||
create mode 100644 changelog/GHSA-qccp-gfcp-xxvc.bugfix.rst
|
||||
|
||||
diff --git a/changelog/GHSA-qccp-gfcp-xxvc.bugfix.rst b/changelog/GHSA-qccp-gfcp-xxvc.bugfix.rst
|
||||
new file mode 100644
|
||||
index 00000000..bac765ea
|
||||
--- /dev/null
|
||||
+++ b/changelog/GHSA-qccp-gfcp-xxvc.bugfix.rst
|
||||
@@ -0,0 +1,3 @@
|
||||
+Fixed HTTP pools created using ``ProxyManager.connection_from_url`` to strip
|
||||
+sensitive headers specified in ``Retry.remove_headers_on_redirect`` when
|
||||
+redirecting to a different host.
|
||||
diff --git a/dummyserver/asgi_proxy.py b/dummyserver/asgi_proxy.py
|
||||
index 107c5e0a..094807cd 100755
|
||||
--- a/dummyserver/asgi_proxy.py
|
||||
+++ b/dummyserver/asgi_proxy.py
|
||||
@@ -52,6 +52,7 @@ class ProxyApp:
|
||||
client_response = await client.request(
|
||||
method=scope["method"],
|
||||
url=scope["path"],
|
||||
+ params=scope["query_string"].decode(),
|
||||
headers=list(scope["headers"]),
|
||||
content=await _read_body(receive),
|
||||
)
|
||||
diff --git a/src/urllib3/connectionpool.py b/src/urllib3/connectionpool.py
|
||||
index a2c3cf60..f64ee2f8 100644
|
||||
--- a/src/urllib3/connectionpool.py
|
||||
+++ b/src/urllib3/connectionpool.py
|
||||
@@ -898,6 +898,18 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
body = None
|
||||
headers = HTTPHeaderDict(headers)._prepare_for_method_change()
|
||||
|
||||
+ # Strip headers marked as unsafe to forward to the redirected location.
|
||||
+ # Check remove_headers_on_redirect to avoid a potential network call within
|
||||
+ # self.is_same_host() which may use socket.gethostbyname() in the future.
|
||||
+ if retries.remove_headers_on_redirect and not self.is_same_host(
|
||||
+ redirect_location
|
||||
+ ):
|
||||
+ new_headers = headers.copy() # type: ignore[union-attr]
|
||||
+ for header in headers:
|
||||
+ if header.lower() in retries.remove_headers_on_redirect:
|
||||
+ new_headers.pop(header, None)
|
||||
+ headers = new_headers
|
||||
+
|
||||
try:
|
||||
retries = retries.increment(method, url, response=response, _pool=self)
|
||||
except MaxRetryError:
|
||||
diff --git a/test/with_dummyserver/test_proxy_poolmanager.py b/test/with_dummyserver/test_proxy_poolmanager.py
|
||||
index 397181a9..a0b11726 100644
|
||||
--- a/test/with_dummyserver/test_proxy_poolmanager.py
|
||||
+++ b/test/with_dummyserver/test_proxy_poolmanager.py
|
||||
@@ -37,6 +37,7 @@ from urllib3.exceptions import (
|
||||
SSLError,
|
||||
)
|
||||
from urllib3.poolmanager import ProxyManager, proxy_from_url
|
||||
+from urllib3.util.retry import RequestHistory
|
||||
from urllib3.util.ssl_ import create_urllib3_context
|
||||
from urllib3.util.timeout import Timeout
|
||||
|
||||
@@ -299,6 +300,77 @@ class TestHTTPProxyManager(HypercornDummyProxyTestCase):
|
||||
assert r._pool is not None
|
||||
assert r._pool.host != self.http_host_alt
|
||||
|
||||
+ _sensitive_headers = {
|
||||
+ "Authorization": "foo",
|
||||
+ "Proxy-Authorization": "bar",
|
||||
+ "Cookie": "foo=bar",
|
||||
+ }
|
||||
+
|
||||
+ @pytest.mark.parametrize(
|
||||
+ "sensitive_headers",
|
||||
+ (_sensitive_headers, {k.lower(): v for k, v in _sensitive_headers.items()}),
|
||||
+ ids=("capitalized", "lowercase"),
|
||||
+ )
|
||||
+ def test_cross_host_redirect_remove_headers_via_proxy_manager(
|
||||
+ self, sensitive_headers: dict[str, str]
|
||||
+ ) -> None:
|
||||
+ headers_url = f"{self.http_url_alt}/headers"
|
||||
+ initial_url = f"{self.http_url}/redirect?target={headers_url}"
|
||||
+ with proxy_from_url(self.proxy_url) as proxy_mgr:
|
||||
+ r = proxy_mgr.request(
|
||||
+ "GET", initial_url, headers=sensitive_headers, retries=1
|
||||
+ )
|
||||
+ assert r.status == 200
|
||||
+ assert r.retries is not None
|
||||
+ assert r.retries.history == (
|
||||
+ RequestHistory(
|
||||
+ method="GET",
|
||||
+ url=initial_url,
|
||||
+ error=None,
|
||||
+ status=303,
|
||||
+ redirect_location=headers_url,
|
||||
+ ),
|
||||
+ )
|
||||
+ data = r.json()
|
||||
+ for header in sensitive_headers:
|
||||
+ assert header not in data
|
||||
+
|
||||
+ @pytest.mark.parametrize(
|
||||
+ "sensitive_headers",
|
||||
+ (_sensitive_headers, {k.lower(): v for k, v in _sensitive_headers.items()}),
|
||||
+ ids=("capitalized", "lowercase"),
|
||||
+ )
|
||||
+ def test_cross_host_redirect_remove_headers_via_pool(
|
||||
+ self, sensitive_headers: dict[str, str]
|
||||
+ ) -> None:
|
||||
+ headers_url = f"{self.http_url_alt}/headers"
|
||||
+ initial_url = f"{self.http_url}/redirect?target={headers_url}"
|
||||
+ with proxy_from_url(self.proxy_url) as proxy_mgr:
|
||||
+ pool = proxy_mgr.connection_from_url(self.http_url)
|
||||
+ r = pool.urlopen(
|
||||
+ "GET",
|
||||
+ initial_url,
|
||||
+ headers=sensitive_headers,
|
||||
+ retries=1,
|
||||
+ redirect=True,
|
||||
+ assert_same_host=False,
|
||||
+ preload_content=True,
|
||||
+ )
|
||||
+ assert r.status == 200
|
||||
+ assert r.retries is not None
|
||||
+ assert r.retries.history == (
|
||||
+ RequestHistory(
|
||||
+ method="GET",
|
||||
+ url=initial_url,
|
||||
+ error=None,
|
||||
+ status=303,
|
||||
+ redirect_location=headers_url,
|
||||
+ ),
|
||||
+ )
|
||||
+ data = r.json()
|
||||
+ for header in sensitive_headers:
|
||||
+ assert header not in data
|
||||
+
|
||||
def test_cross_protocol_redirect(self) -> None:
|
||||
with proxy_from_url(self.proxy_url, ca_certs=DEFAULT_CA) as http:
|
||||
cross_protocol_location = f"{self.https_url}/echo?a=b"
|
||||
Reference in New Issue
Block a user