python-urllib3/CVE-2026-44431.patch
Tomáš Hrnčiar b520843218 Security fix for CVE-2026-44431 and CVE-2026-44432
- Resolves: RHEL-184817
- Resolves: RHEL-185121
2026-06-16 14:40:20 +02:00

149 lines
5.6 KiB
Diff

From 7d77cb649bbbb46711d39da4075a2b0f95894fcd Mon Sep 17 00:00:00 2001
From: Illia Volochii <illia.volochii@gmail.com>
Date: Wed, 3 Jun 2026 12:48:11 +0200
Subject: [PATCH] CVE-2026-44431
* Remove sensitive headers in proxy pools too
* Add a changelog entry
* Check retries history in tests
Co-authored-by: Copilot <copilot@github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
---
changelog/GHSA-qccp-gfcp-xxvc.bugfix.rst | 3 +
src/urllib3/connectionpool.py | 12 ++++
.../test_proxy_poolmanager.py | 72 +++++++++++++++++++
3 files changed, 87 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 0000000..bac765e
--- /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/src/urllib3/connectionpool.py b/src/urllib3/connectionpool.py
index 402bf67..9466b32 100644
--- a/src/urllib3/connectionpool.py
+++ b/src/urllib3/connectionpool.py
@@ -852,6 +852,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 7b292a2..b8745fc 100644
--- a/test/with_dummyserver/test_proxy_poolmanager.py
+++ b/test/with_dummyserver/test_proxy_poolmanager.py
@@ -34,6 +34,7 @@ from urllib3.exceptions import (
SubjectAltNameWarning,
)
from urllib3.poolmanager import ProxyManager, proxy_from_url
+from urllib3.util.retry import RequestHistory
from urllib3.util import Timeout
from urllib3.util.ssl_ import create_urllib3_context
@@ -277,6 +278,77 @@ class TestHTTPProxyManager(HTTPDummyProxyTestCase):
)
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):
with proxy_from_url(self.proxy_url, ca_certs=DEFAULT_CA) as http:
cross_protocol_location = "%s/echo?a=b" % self.https_url
--
2.54.0