|
|
38727c |
From 73474e1b518ce91481b7101d68d8a75c7ad67029 Mon Sep 17 00:00:00 2001
|
|
|
38727c |
From: Lumir Balhar <lbalhar@redhat.com>
|
|
|
38727c |
Date: Mon, 4 Mar 2019 12:45:09 +0100
|
|
|
38727c |
Subject: [PATCH] CVE-2018-20060
|
|
|
38727c |
|
|
|
38727c |
Cross-host redirect does not remove Authorization header allow
|
|
|
38727c |
for credential exposure.
|
|
|
38727c |
---
|
|
|
38727c |
CHANGES.rst | 4 ++++
|
|
|
38727c |
urllib3/poolmanager.py | 11 ++++++++++-
|
|
|
38727c |
urllib3/util/retry.py | 12 +++++++++++-
|
|
|
38727c |
3 files changed, 25 insertions(+), 2 deletions(-)
|
|
|
38727c |
|
|
|
38727c |
diff --git a/CHANGES.rst b/CHANGES.rst
|
|
|
38727c |
index 0150d85..34ad6b0 100644
|
|
|
38727c |
--- a/CHANGES.rst
|
|
|
38727c |
+++ b/CHANGES.rst
|
|
|
38727c |
@@ -1,6 +1,10 @@
|
|
|
38727c |
Changes
|
|
|
38727c |
=======
|
|
|
38727c |
|
|
|
38727c |
+* Allow providing a list of headers to strip from requests when redirecting
|
|
|
38727c |
+ to a different host. Defaults to the ``Authorization`` header. Different
|
|
|
38727c |
+ headers can be set via ``Retry.remove_headers_on_redirect``. (Issue #1316)
|
|
|
38727c |
+
|
|
|
38727c |
* Accept ``iPAddress`` subject alternative name fields in TLS certificates.
|
|
|
38727c |
(Issue #258)
|
|
|
38727c |
|
|
|
38727c |
diff --git a/urllib3/poolmanager.py b/urllib3/poolmanager.py
|
|
|
38727c |
index 4fdae8d..b43b235 100644
|
|
|
38727c |
--- a/urllib3/poolmanager.py
|
|
|
38727c |
+++ b/urllib3/poolmanager.py
|
|
|
38727c |
@@ -238,8 +238,9 @@ class PoolManager(RequestMethods):
|
|
|
38727c |
|
|
|
38727c |
kw['assert_same_host'] = False
|
|
|
38727c |
kw['redirect'] = False
|
|
|
38727c |
+
|
|
|
38727c |
if 'headers' not in kw:
|
|
|
38727c |
- kw['headers'] = self.headers
|
|
|
38727c |
+ kw['headers'] = self.headers.copy()
|
|
|
38727c |
|
|
|
38727c |
if self.proxy is not None and u.scheme == "http":
|
|
|
38727c |
response = conn.urlopen(method, url, **kw)
|
|
|
38727c |
@@ -261,6 +262,14 @@ class PoolManager(RequestMethods):
|
|
|
38727c |
if not isinstance(retries, Retry):
|
|
|
38727c |
retries = Retry.from_int(retries, redirect=redirect)
|
|
|
38727c |
|
|
|
38727c |
+ # Strip headers marked as unsafe to forward to the redirected location.
|
|
|
38727c |
+ # Check remove_headers_on_redirect to avoid a potential network call within
|
|
|
38727c |
+ # conn.is_same_host() which may use socket.gethostbyname() in the future.
|
|
|
38727c |
+ if (retries.remove_headers_on_redirect
|
|
|
38727c |
+ and not conn.is_same_host(redirect_location)):
|
|
|
38727c |
+ for header in retries.remove_headers_on_redirect:
|
|
|
38727c |
+ kw['headers'].pop(header, None)
|
|
|
38727c |
+
|
|
|
38727c |
try:
|
|
|
38727c |
retries = retries.increment(method, url, response=response, _pool=conn)
|
|
|
38727c |
except MaxRetryError:
|
|
|
38727c |
diff --git a/urllib3/util/retry.py b/urllib3/util/retry.py
|
|
|
38727c |
index 7e0959d..0cf8eed 100644
|
|
|
38727c |
--- a/urllib3/util/retry.py
|
|
|
38727c |
+++ b/urllib3/util/retry.py
|
|
|
38727c |
@@ -101,17 +101,25 @@ class Retry(object):
|
|
|
38727c |
:param bool raise_on_redirect: Whether, if the number of redirects is
|
|
|
38727c |
exhausted, to raise a MaxRetryError, or to return a response with a
|
|
|
38727c |
response code in the 3xx range.
|
|
|
38727c |
+
|
|
|
38727c |
+ :param iterable remove_headers_on_redirect:
|
|
|
38727c |
+ Sequence of headers to remove from the request when a response
|
|
|
38727c |
+ indicating a redirect is returned before firing off the redirected
|
|
|
38727c |
+ request.
|
|
|
38727c |
"""
|
|
|
38727c |
|
|
|
38727c |
DEFAULT_METHOD_WHITELIST = frozenset([
|
|
|
38727c |
'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'])
|
|
|
38727c |
|
|
|
38727c |
+ DEFAULT_REDIRECT_HEADERS_BLACKLIST = frozenset(['Authorization'])
|
|
|
38727c |
+
|
|
|
38727c |
#: Maximum backoff time.
|
|
|
38727c |
BACKOFF_MAX = 120
|
|
|
38727c |
|
|
|
38727c |
def __init__(self, total=10, connect=None, read=None, redirect=None,
|
|
|
38727c |
method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None,
|
|
|
38727c |
- backoff_factor=0, raise_on_redirect=True, _observed_errors=0):
|
|
|
38727c |
+ backoff_factor=0, raise_on_redirect=True, _observed_errors=0,
|
|
|
38727c |
+ remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST):
|
|
|
38727c |
|
|
|
38727c |
self.total = total
|
|
|
38727c |
self.connect = connect
|
|
|
38727c |
@@ -127,6 +135,7 @@ class Retry(object):
|
|
|
38727c |
self.backoff_factor = backoff_factor
|
|
|
38727c |
self.raise_on_redirect = raise_on_redirect
|
|
|
38727c |
self._observed_errors = _observed_errors # TODO: use .history instead?
|
|
|
38727c |
+ self.remove_headers_on_redirect = remove_headers_on_redirect
|
|
|
38727c |
|
|
|
38727c |
def new(self, **kw):
|
|
|
38727c |
params = dict(
|
|
|
38727c |
@@ -137,6 +146,7 @@ class Retry(object):
|
|
|
38727c |
backoff_factor=self.backoff_factor,
|
|
|
38727c |
raise_on_redirect=self.raise_on_redirect,
|
|
|
38727c |
_observed_errors=self._observed_errors,
|
|
|
38727c |
+ remove_headers_on_redirect=self.remove_headers_on_redirect,
|
|
|
38727c |
)
|
|
|
38727c |
params.update(kw)
|
|
|
38727c |
return type(self)(**params)
|
|
|
38727c |
--
|
|
|
38727c |
2.20.1
|
|
|
38727c |
|