From 20f7d039446f71c40ea240c0a9f6e2465659ec68 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Mon, 13 Jan 2020 09:31:42 +0100 Subject: [PATCH 1/2] CVE-2018-20060 --- poolmanager.py | 11 ++++++++++- util/retry.py | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/poolmanager.py b/poolmanager.py index 1023dcb..b87f315 100644 --- a/poolmanager.py +++ b/poolmanager.py @@ -156,8 +156,9 @@ class PoolManager(RequestMethods): kw['assert_same_host'] = False kw['redirect'] = False + if 'headers' not in kw: - kw['headers'] = self.headers + kw['headers'] = self.headers.copy() if self.proxy is not None and u.scheme == "http": response = conn.urlopen(method, url, **kw) @@ -179,6 +180,14 @@ class PoolManager(RequestMethods): if not isinstance(retries, Retry): retries = Retry.from_int(retries, redirect=redirect) + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # 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) + try: retries = retries.increment(method, url, response=response, _pool=conn) except MaxRetryError: diff --git a/util/retry.py b/util/retry.py index 2d3aa20..d6bd74e 100644 --- a/util/retry.py +++ b/util/retry.py @@ -103,6 +103,11 @@ class Retry(object): exhausted, to raise a MaxRetryError, or to return a response with a response code in the 3xx range. + :param iterable remove_headers_on_redirect: + Sequence of headers to remove from the request when a response + indicating a redirect is returned before firing off the redirected + request + :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: whether we should raise an exception, or return a response, if status falls in ``status_forcelist`` range and retries have @@ -112,13 +117,15 @@ class Retry(object): DEFAULT_METHOD_WHITELIST = frozenset([ 'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']) + DEFAULT_REDIRECT_HEADERS_BLACKLIST = frozenset(['Authorization']) + #: Maximum backoff time. BACKOFF_MAX = 120 def __init__(self, total=10, connect=None, read=None, redirect=None, method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None, backoff_factor=0, raise_on_redirect=True, raise_on_status=True, - _observed_errors=0): + _observed_errors=0, remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST): self.total = total self.connect = connect @@ -135,6 +142,7 @@ class Retry(object): self.raise_on_redirect = raise_on_redirect self.raise_on_status = raise_on_status self._observed_errors = _observed_errors # TODO: use .history instead? + self.remove_headers_on_redirect = remove_headers_on_redirect def new(self, **kw): params = dict( @@ -146,6 +154,7 @@ class Retry(object): raise_on_redirect=self.raise_on_redirect, raise_on_status=self.raise_on_status, _observed_errors=self._observed_errors, + remove_headers_on_redirect=self.remove_headers_on_redirect, ) params.update(kw) return type(self)(**params) -- 2.24.1