CVE-2018-25091
Description
urllib3 before 1.24.2 does not remove the authorization HTTP header when following a cross-origin redirect (i.e., a redirect that differs in host, port, or scheme). This can allow for credentials in the authorization header to be exposed to unintended hosts or transmitted in cleartext. NOTE: this issue exists because of an incomplete fix for CVE-2018-20060 (which was case-sensitive).
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Urllib3 before 1.24.2 improperly retains the Authorization header on cross-origin redirects, exposing credentials to unintended hosts.
Vulnerability
Overview
CVE-2018-25091 is a security flaw in urllib3, the popular Python HTTP client library, affecting versions prior to 1.24.2. The vulnerability arises because the library does not remove the Authorization HTTP header when following a cross-origin redirect, i.e., a redirect that changes the host, port, or scheme [1]. This behavior can leak sensitive credentials (such as Basic Auth tokens) to unintended servers or even transmit them in cleartext over unencrypted connections. Notably, this issue exists due to an incomplete fix for CVE-2018-20060, which used a case-sensitive header removal that could be bypassed [1].
Attack
Vector and Exploitation
An attacker can exploit this by setting up a server that issues an HTTP redirect to a different origin; when the victim's application (using a vulnerable urllib3 version) follows that redirect, the Authorization header is forwarded to the attacker-controlled host. No additional authentication is needed from the attacker beyond being able to craft a redirect response. The fix, introduced in commit adb358f, makes the header removal case-insensitive by converting all headers to lowercase before checking against the removal list, preventing the bypass [2].
Impact and
Mitigation
If exploited, an attacker can obtain credentials (e.g., username and password) that were intended for a legitimate service, potentially leading to account compromise or unauthorized access to protected resources. The vulnerability is scored with CVSS v4.0 metrics not yet provided by NVD, but the issue is classified as a high-severity information disclosure [1]. Users are strongly advised to upgrade to urllib3 version 1.24.2 or later. As of this writing, there are no known workarounds beyond updating the library [1][2].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
urllib3PyPI | < 1.24.2 | 1.24.2 |
Affected products
4- urllib3/urllib3description
- ghsa-coords3 versionspkg:pypi/urllib3pkg:rpm/suse/python-urllib3&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/python-urllib3&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209
< 1.24.2+ 2 more
- (no CPE)range: < 1.24.2
- (no CPE)range: < 1.23-3.25.1
- (no CPE)range: < 1.23-3.25.1
Patches
1adb358f8e068Remove Authorization headers regardless of case on cross-origin redirects (#1511)
6 files changed · +39 −5
CHANGES.rst+2 −0 modified@@ -8,6 +8,8 @@ dev (master) * Upgraded ``urllib3.utils.parse_url()`` to be RFC 3986 compliant. (Pull #1487) +* Remove Authorization header regardless of case when redirecting to cross-site. (Issue #1510) + * ... [Short description of non-trivial change.] (Issue #)
CONTRIBUTORS.txt+3 −0 modified@@ -272,5 +272,8 @@ In chronological order: * Justin Bramley <https://github.com/jbramleycl> * Add ability to handle multiple Content-Encodings +* Katsuhiko YOSHIDA <https://github.com/kyoshidajp> + * Remove Authorization header regardless of case when redirecting to cross-site + * [Your name or handle] <[email or website]> * [Brief summary of your changes]
src/urllib3/poolmanager.py+5 −2 modified@@ -7,6 +7,7 @@ from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool from .connectionpool import port_by_scheme from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown +from .packages import six from .packages.six.moves.urllib.parse import urljoin from .request import RequestMethods from .util.url import parse_url @@ -342,8 +343,10 @@ def urlopen(self, method, url, redirect=True, **kw): # conn.is_same_host() which may use socket.gethostbyname() in the future. if (retries.remove_headers_on_redirect and not conn.is_same_host(redirect_location)): - for header in retries.remove_headers_on_redirect: - kw['headers'].pop(header, None) + headers = list(six.iterkeys(kw['headers'])) + for header in headers: + if header.lower() in retries.remove_headers_on_redirect: + kw['headers'].pop(header, None) try: retries = retries.increment(method, url, response=response, _pool=conn)
src/urllib3/util/retry.py+2 −1 modified@@ -179,7 +179,8 @@ def __init__(self, total=10, connect=None, read=None, redirect=None, status=None self.raise_on_status = raise_on_status self.history = history or tuple() self.respect_retry_after_header = respect_retry_after_header - self.remove_headers_on_redirect = remove_headers_on_redirect + self.remove_headers_on_redirect = frozenset([ + h.lower() for h in remove_headers_on_redirect]) def new(self, **kw): params = dict(
test/test_retry.py+2 −2 modified@@ -253,9 +253,9 @@ def test_retry_method_not_in_whitelist(self): def test_retry_default_remove_headers_on_redirect(self): retry = Retry() - assert list(retry.remove_headers_on_redirect) == ['Authorization'] + assert list(retry.remove_headers_on_redirect) == ['authorization'] def test_retry_set_remove_headers_on_redirect(self): retry = Retry(remove_headers_on_redirect=['X-API-Secret']) - assert list(retry.remove_headers_on_redirect) == ['X-API-Secret'] + assert list(retry.remove_headers_on_redirect) == ['x-api-secret']
test/with_dummyserver/test_poolmanager.py+25 −0 modified@@ -123,6 +123,17 @@ def test_redirect_cross_host_remove_headers(self): self.assertNotIn('Authorization', data) + r = http.request('GET', '%s/redirect' % self.base_url, + fields={'target': '%s/headers' % self.base_url_alt}, + headers={'authorization': 'foo'}) + + self.assertEqual(r.status, 200) + + data = json.loads(r.data.decode('utf-8')) + + self.assertNotIn('authorization', data) + self.assertNotIn('Authorization', data) + def test_redirect_cross_host_no_remove_headers(self): http = PoolManager() self.addCleanup(http.clear) @@ -155,6 +166,20 @@ def test_redirect_cross_host_set_removed_headers(self): self.assertNotIn('X-API-Secret', data) self.assertEqual(data['Authorization'], 'bar') + r = http.request('GET', '%s/redirect' % self.base_url, + fields={'target': '%s/headers' % self.base_url_alt}, + headers={'x-api-secret': 'foo', + 'authorization': 'bar'}, + retries=Retry(remove_headers_on_redirect=['X-API-Secret'])) + + self.assertEqual(r.status, 200) + + data = json.loads(r.data.decode('utf-8')) + + self.assertNotIn('x-api-secret', data) + self.assertNotIn('X-API-Secret', data) + self.assertEqual(data['Authorization'], 'bar') + def test_raise_on_redirect(self): http = PoolManager() self.addCleanup(http.clear)
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
6- github.com/advisories/GHSA-gwvm-45gx-3cf8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-25091ghsaADVISORY
- github.com/pypa/advisory-database/tree/main/vulns/urllib3/PYSEC-2023-207.yamlghsaWEB
- github.com/urllib3/urllib3/commit/adb358f8e06865406d1f05e581a16cbea2136fbcghsaWEB
- github.com/urllib3/urllib3/compare/1.24.1...1.24.2ghsaWEB
- github.com/urllib3/urllib3/issues/1510ghsaWEB
News mentions
0No linked articles in our index yet.