Requests is a HTTP library. Since Requests 2.3.0, Requests has been leaking Proxy-Authorization headers to destination servers when redirected to an HTTPS endpoint. This is a product of how we use `re
Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.
Affected versions
Details
Requests is a HTTP library. Since Requests 2.3.0, Requests has been leaking Proxy-Authorization headers to destination servers when redirected to an HTTPS endpoint. This is a product of how we use `rebuild_proxies` to reattach the `Proxy-Authorization` header to requests. For HTTP connections sent through the tunnel, the proxy will identify the header in the request itself and remove it prior to forwarding to the destination server. However when sent over HTTPS, the `Proxy-Authorization` header must be sent in the CONNECT request as the proxy has no visibility into the tunneled request. This results in Requests forwarding proxy credentials to the destination server unintentionally, allowing a malicious actor to potentially exfiltrate sensitive information. This issue has been patched in version 2.31.0.
The fix
Merge pull request from GHSA-j8r2-6x86-q33q
requests/sessions.py+3 −1
@@ -324,7 +324,9 @@ def rebuild_proxies(self, prepared_request, proxies):except KeyError:username, password = None, None-if username and password:+# urllib3 handles proxy authorization for us in the standard adapter.+# Avoid appending this to TLS tunneled requests where it may be leaked.+if not scheme.startswith('https') and username and password:headers["Proxy-Authorization"] = _basic_auth_str(username, password)return new_proxies
tests/test_requests.py+20 −0
@@ -647,6 +647,26 @@ def test_proxy_authorization_preserved_on_request(self, httpbin):assert sent_headers.get("Proxy-Authorization") == proxy_auth_value++@pytest.mark.parametrize(+"url,has_proxy_auth",+(+('http://example.com', True),+('https://example.com', False),+),+)+def test_proxy_authorization_not_appended_to_https_request(self, url, has_proxy_auth):+session = requests.Session()+proxies = {+'http': 'http://test:pass@localhost:8080',+'https': 'http://test:pass@localhost:8090',+}+req = requests.Request('GET', url)+prep = req.prepare()+session.rebuild_proxies(prep, proxies)++assert ('Proxy-Authorization' in prep.headers) is has_proxy_auth+def test_basicauth_with_netrc(self, httpbin):auth = ("user", "pass")wrong_auth = ("wronguser", "wrongpass")
References
- ADVISORYhttps://github.com/psf/requests/security/advisories/GHSA-j8r2-6x86-q33q
- WEBhttps://github.com/psf/requests/releases/tag/v2.31.0
- FIXhttps://github.com/psf/requests/commit/74ea7cf7a6a27a4eeb2ae24e162bcc942a6706d5
- WEBhttps://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/AW7HNFGYP44RT3DUDQXG2QT3OEV2PJ7Y/