diff -Nru python-urllib3-1.13.1/debian/changelog python-urllib3-1.13.1/debian/changelog --- python-urllib3-1.13.1/debian/changelog 2018-08-20 14:54:33.000000000 +0000 +++ python-urllib3-1.13.1/debian/changelog 2019-05-14 12:37:37.000000000 +0000 @@ -1,3 +1,21 @@ +python-urllib3 (1.13.1-2ubuntu0.16.04.3) xenial-security; urgency=medium + + * SECURITY UPDATE: credential disclosure via cross-origin redirect + - debian/patches/CVE-2018-20060-*.patch: backport logic to strip + Authorization header when following a cross-origin redirect. + - CVE-2018-20060 + * SECURITY UPDATE: CRLF injection issue + - debian/patches/CVE-2019-11236-1.patch: check for control chars in URL + in urllib3/connection.py, urllib3/contrib/pyopenssl.py, + urllib3/util/url.py, test/test_util.py. + - debian/patches/CVE-2019-11236-2.patch: percent-encode invalid target + characters in urllib3/util/url.py, test/test_util.py. + - debian/patches/CVE-2019-11236-3.patch: don't use embedded python-six + in urllib3/util/url.py. + - CVE-2019-11236 + + -- Marc Deslauriers Mon, 13 May 2019 15:26:05 -0400 + python-urllib3 (1.13.1-2ubuntu0.16.04.2) xenial; urgency=medium * d/p/07_support_ip_sans.patch: Cherry pick fix to support use of diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2018-20060-10.patch python-urllib3-1.13.1/debian/patches/CVE-2018-20060-10.patch --- python-urllib3-1.13.1/debian/patches/CVE-2018-20060-10.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2018-20060-10.patch 2019-05-13 19:22:57.000000000 +0000 @@ -0,0 +1,42 @@ +Backport of: + +From 2a42e70ff077006d5a6da92251ddbb2939303f94 Mon Sep 17 00:00:00 2001 +From: "Seth M. Larson" +Date: Tue, 27 Mar 2018 14:44:51 -0500 +Subject: [PATCH] Move DEFAULT_REDIRECT_HEADERS_BLACKLIST under Retry + +--- + urllib3/util/retry.py | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +Index: python-urllib3-1.13.1/urllib3/util/retry.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/util/retry.py 2019-05-13 15:22:20.434649693 -0400 ++++ python-urllib3-1.13.1/urllib3/util/retry.py 2019-05-13 15:22:35.602707402 -0400 +@@ -14,8 +14,6 @@ import six + + log = logging.getLogger(__name__) + +-DEFAULT_REDIRECT_HEADERS_BLACKLIST = ['Authorization'] +- + + class Retry(object): + """ Retry configuration. +@@ -114,6 +112,8 @@ 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 + +@@ -131,7 +131,7 @@ class Retry(object): + raise_on_redirect = False + + if remove_headers_on_redirect is None: +- remove_headers_on_redirect = DEFAULT_REDIRECT_HEADERS_BLACKLIST ++ remove_headers_on_redirect = self.DEFAULT_REDIRECT_HEADERS_BLACKLIST + remove_headers_on_redirect = set(remove_headers_on_redirect) + + self.redirect = redirect diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2018-20060-12.patch python-urllib3-1.13.1/debian/patches/CVE-2018-20060-12.patch --- python-urllib3-1.13.1/debian/patches/CVE-2018-20060-12.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2018-20060-12.patch 2019-05-13 19:23:47.000000000 +0000 @@ -0,0 +1,35 @@ +Backport of: + +From 63948f3a607ed8e7a3ce9ac4e20782359896e27e Mon Sep 17 00:00:00 2001 +From: "Seth M. Larson" +Date: Wed, 28 Mar 2018 11:06:59 -0500 +Subject: [PATCH] Change remove_headers_on_redirect default value + +--- + urllib3/util/retry.py | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +Index: python-urllib3-1.13.1/urllib3/util/retry.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/util/retry.py 2019-05-13 15:23:08.554832986 -0400 ++++ python-urllib3-1.13.1/urllib3/util/retry.py 2019-05-13 15:23:31.122919157 -0400 +@@ -120,7 +120,7 @@ class Retry(object): + 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, _observed_errors=0, +- remove_headers_on_redirect=None): ++ remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST): + + self.total = total + self.connect = connect +@@ -130,10 +130,6 @@ class Retry(object): + redirect = 0 + raise_on_redirect = False + +- if remove_headers_on_redirect is None: +- remove_headers_on_redirect = self.DEFAULT_REDIRECT_HEADERS_BLACKLIST +- remove_headers_on_redirect = set(remove_headers_on_redirect) +- + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.method_whitelist = method_whitelist diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2018-20060-1.patch python-urllib3-1.13.1/debian/patches/CVE-2018-20060-1.patch --- python-urllib3-1.13.1/debian/patches/CVE-2018-20060-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2018-20060-1.patch 2019-05-13 19:12:45.000000000 +0000 @@ -0,0 +1,54 @@ +Backport of: + +From 3d7f98b07b6e6e04c2e89cdf5afb18024a2d804c Mon Sep 17 00:00:00 2001 +From: SethMichaelLarson +Date: Sun, 25 Mar 2018 20:30:13 -0500 +Subject: [PATCH] Add forward_auth_headers_across_hosts parameter to Retry + +--- + urllib3/util/retry.py | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +Index: python-urllib3-1.13.1/urllib3/util/retry.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/util/retry.py 2019-05-13 15:10:21.240037642 -0400 ++++ python-urllib3-1.13.1/urllib3/util/retry.py 2019-05-13 15:12:17.408435258 -0400 +@@ -102,6 +102,11 @@ class Retry(object): + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. ++ ++ :param bool forward_auth_headers_across_hosts: ++ Whether to forward Authentication headers if a response is received ++ that redirects to a different host than the original request. ++ Defaults to False. + """ + + DEFAULT_METHOD_WHITELIST = frozenset([ +@@ -112,7 +117,8 @@ class Retry(object): + + 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, _observed_errors=0): ++ backoff_factor=0, raise_on_redirect=True, _observed_errors=0, ++ forward_auth_headers_across_hosts=False): + + self.total = total + self.connect = connect +@@ -128,6 +134,8 @@ class Retry(object): + self.backoff_factor = backoff_factor + self.raise_on_redirect = raise_on_redirect + self._observed_errors = _observed_errors # TODO: use .history instead? ++ self.forward_auth_headers_across_hosts = \ ++ forward_auth_headers_across_hosts + + def new(self, **kw): + params = dict( +@@ -138,6 +146,7 @@ class Retry(object): + backoff_factor=self.backoff_factor, + raise_on_redirect=self.raise_on_redirect, + _observed_errors=self._observed_errors, ++ forward_auth_headers_across_hosts=self.forward_auth_headers_across_hosts + ) + params.update(kw) + return type(self)(**params) diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2018-20060-2.patch python-urllib3-1.13.1/debian/patches/CVE-2018-20060-2.patch --- python-urllib3-1.13.1/debian/patches/CVE-2018-20060-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2018-20060-2.patch 2019-05-13 19:15:44.000000000 +0000 @@ -0,0 +1,95 @@ +Backport of: + +From f99912beeaf230ee3634b938d3ea426ffd1f3e57 Mon Sep 17 00:00:00 2001 +From: SethMichaelLarson +Date: Sun, 25 Mar 2018 20:30:47 -0500 +Subject: [PATCH] Add Authentication strip logic to ConnectionPool + +--- + test/with_dummyserver/test_connectionpool.py | 33 +++++++++++++++++++- + urllib3/connectionpool.py | 7 +++++ + 2 files changed, 39 insertions(+), 1 deletion(-) + +Index: python-urllib3-1.13.1/test/with_dummyserver/test_connectionpool.py +=================================================================== +--- python-urllib3-1.13.1.orig/test/with_dummyserver/test_connectionpool.py 2019-05-13 15:12:56.396571608 -0400 ++++ python-urllib3-1.13.1/test/with_dummyserver/test_connectionpool.py 2019-05-13 15:13:48.068754279 -0400 +@@ -5,6 +5,7 @@ import sys + import unittest + import time + import warnings ++import json + + import mock + +@@ -15,7 +16,10 @@ except: + + from .. import ( + requires_network, onlyPy3, onlyPy26OrOlder, +- TARPIT_HOST, VALID_SOURCE_ADDRESSES, INVALID_SOURCE_ADDRESSES, ++ TARPIT_HOST, ++ VALID_SOURCE_ADDRESSES, ++ INVALID_SOURCE_ADDRESSES, ++ requires_network + ) + from ..port_helpers import find_unused_port + from urllib3 import ( +@@ -692,6 +696,8 @@ class TestRetry(HTTPDummyServerTestCase) + def setUp(self): + self.pool = HTTPConnectionPool(self.host, self.port) + ++ self.base_url_alt = 'http://%s:%d' % (self.host_alt, self.port) ++ + def test_max_retry(self): + try: + r = self.pool.request('GET', '/redirect', +@@ -774,6 +780,31 @@ class TestRetry(HTTPDummyServerTestCase) + headers=headers, retries=retry) + self.assertEqual(resp.status, 200) + ++ def test_redirect_cross_host_no_forward_auth_headers(self): ++ url = '/redirect?target=%s/headers' % self.base_url_alt ++ headers = {'Authentication': 'foo'} ++ ++ resp = self.pool.urlopen('GET', url, headers=headers, assert_same_host=False) ++ ++ self.assertEqual(resp.status, 200) ++ ++ data = json.loads(resp.data.decode('utf-8')) ++ ++ self.assertNotIn('Authentication', data) ++ ++ def test_redirect_cross_host_forward_auth_headers(self): ++ url = '/redirect?target=%s/headers' % self.base_url_alt ++ headers = {'Authentication': 'foo'} ++ retry = Retry(redirect=2, forward_auth_headers_across_hosts=True) ++ ++ resp = self.pool.urlopen('GET', url, headers=headers, assert_same_host=False, retries=retry) ++ ++ self.assertEqual(resp.status, 200) ++ ++ data = json.loads(resp.data.decode('utf-8')) ++ ++ self.assertEqual(data['Authentication'], 'foo') ++ + + if __name__ == '__main__': + unittest.main() +Index: python-urllib3-1.13.1/urllib3/connectionpool.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/connectionpool.py 2019-05-13 15:12:56.396571608 -0400 ++++ python-urllib3-1.13.1/urllib3/connectionpool.py 2019-05-13 15:15:24.381100053 -0400 +@@ -645,6 +645,13 @@ class HTTPConnectionPool(ConnectionPool, + raise + return response + ++ # Strip the Authentication header if redirecting to a new host ++ # and we don't want to forward that header across hosts. ++ if (not retries.forward_auth_headers_across_hosts ++ and 'Authentication' in headers ++ and not self.is_same_host(redirect_location)): ++ headers.pop('Authentication') ++ + log.info("Redirecting %s -> %s" % (url, redirect_location)) + return self.urlopen( + method, redirect_location, body, headers, diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2018-20060-3.patch python-urllib3-1.13.1/debian/patches/CVE-2018-20060-3.patch --- python-urllib3-1.13.1/debian/patches/CVE-2018-20060-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2018-20060-3.patch 2019-05-13 19:15:55.000000000 +0000 @@ -0,0 +1,89 @@ +From 48dba048081dfcb999afcda715d17147aa15b6ea Mon Sep 17 00:00:00 2001 +From: SethMichaelLarson +Date: Sun, 25 Mar 2018 20:31:10 -0500 +Subject: [PATCH] Add Authentication strip logic to PoolManager + +--- + test/with_dummyserver/test_poolmanager.py | 29 +++++++++++++++++++++++ + urllib3/poolmanager.py | 13 +++++++++- + 2 files changed, 41 insertions(+), 1 deletion(-) + +Index: python-urllib3-1.13.1/test/with_dummyserver/test_poolmanager.py +=================================================================== +--- python-urllib3-1.13.1.orig/test/with_dummyserver/test_poolmanager.py 2019-05-13 15:15:53.045204146 -0400 ++++ python-urllib3-1.13.1/test/with_dummyserver/test_poolmanager.py 2019-05-13 15:15:53.041204131 -0400 +@@ -100,6 +100,35 @@ class TestPoolManager(HTTPDummyServerTes + except MaxRetryError: + pass + ++ def test_redirect_cross_host_strip_auth_headers(self): ++ http = PoolManager() ++ self.addCleanup(http.clear) ++ ++ r = http.request('GET', '%s/redirect' % self.base_url, ++ fields={'target': '%s/headers' % self.base_url_alt}, ++ headers={'Authentication': 'foo'}) ++ ++ self.assertEqual(r.status, 200) ++ ++ data = json.loads(r.data.decode('utf-8')) ++ ++ self.assertNotIn('Authentication', data) ++ ++ def test_redirect_cross_host_forward_auth_headers(self): ++ http = PoolManager() ++ self.addCleanup(http.clear) ++ ++ r = http.request('GET', '%s/redirect' % self.base_url, ++ fields={'target': '%s/headers' % self.base_url_alt}, ++ headers={'Authentication': 'foo'}, ++ retries=Retry(forward_auth_headers_across_hosts=True)) ++ ++ self.assertEqual(r.status, 200) ++ ++ data = json.loads(r.data.decode('utf-8')) ++ ++ self.assertEqual(data['Authentication'], 'foo') ++ + def test_raise_on_redirect(self): + http = PoolManager() + +Index: python-urllib3-1.13.1/urllib3/poolmanager.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/poolmanager.py 2019-05-13 15:15:53.045204146 -0400 ++++ python-urllib3-1.13.1/urllib3/poolmanager.py 2019-05-13 15:15:53.041204131 -0400 +@@ -153,8 +153,11 @@ class PoolManager(RequestMethods): + + kw['assert_same_host'] = False + kw['redirect'] = False ++ + if 'headers' not in kw: +- kw['headers'] = self.headers ++ kw['headers'] = headers = self.headers ++ else: ++ headers = kw['headers'] + + if self.proxy is not None and u.scheme == "http": + response = conn.urlopen(method, url, **kw) +@@ -176,6 +179,13 @@ class PoolManager(RequestMethods): + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + ++ # Strip the Authentication header if redirecting to a new host ++ # and we don't want to forward that header across hosts. ++ if (not retries.forward_auth_headers_across_hosts ++ and 'Authentication' in headers ++ and not conn.is_same_host(redirect_location)): ++ headers.pop('Authentication') ++ + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: +@@ -185,6 +195,7 @@ class PoolManager(RequestMethods): + + kw['retries'] = retries + kw['redirect'] = redirect ++ kw['headers'] = headers + + log.info("Redirecting %s -> %s" % (url, redirect_location)) + return self.urlopen(method, redirect_location, **kw) diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2018-20060-5.patch python-urllib3-1.13.1/debian/patches/CVE-2018-20060-5.patch --- python-urllib3-1.13.1/debian/patches/CVE-2018-20060-5.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2018-20060-5.patch 2019-05-13 19:16:47.000000000 +0000 @@ -0,0 +1,39 @@ +Backport of: + +From 5e9c6b9175d66170ef65fc703f2e46788a59ca0c Mon Sep 17 00:00:00 2001 +From: SethMichaelLarson +Date: Sun, 25 Mar 2018 20:52:13 -0500 +Subject: [PATCH] Fix AppEngine connection pool tests + +--- + test/appengine/test_gae_manager.py | 4 ++++ + test/with_dummyserver/test_connectionpool.py | 9 ++++++--- + 2 files changed, 10 insertions(+), 3 deletions(-) + +Index: python-urllib3-1.13.1/test/with_dummyserver/test_connectionpool.py +=================================================================== +--- python-urllib3-1.13.1.orig/test/with_dummyserver/test_connectionpool.py 2019-05-13 15:16:33.925353443 -0400 ++++ python-urllib3-1.13.1/test/with_dummyserver/test_connectionpool.py 2019-05-13 15:16:33.925353443 -0400 +@@ -18,8 +18,7 @@ from .. import ( + requires_network, onlyPy3, onlyPy26OrOlder, + TARPIT_HOST, + VALID_SOURCE_ADDRESSES, +- INVALID_SOURCE_ADDRESSES, +- requires_network ++ INVALID_SOURCE_ADDRESSES + ) + from ..port_helpers import find_unused_port + from urllib3 import ( +@@ -797,7 +796,11 @@ class TestRetry(HTTPDummyServerTestCase) + headers = {'Authentication': 'foo'} + retry = Retry(redirect=2, forward_auth_headers_across_hosts=True) + +- resp = self.pool.urlopen('GET', url, headers=headers, assert_same_host=False, retries=retry) ++ resp = self.pool.urlopen( ++ 'GET', url, headers=headers, ++ assert_same_host=False, ++ retries=retry ++ ) + + self.assertEqual(resp.status, 200) + diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2018-20060-6.patch python-urllib3-1.13.1/debian/patches/CVE-2018-20060-6.patch --- python-urllib3-1.13.1/debian/patches/CVE-2018-20060-6.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2018-20060-6.patch 2019-05-13 19:20:23.000000000 +0000 @@ -0,0 +1,284 @@ +Backport of: + +From 9c9dd6f3014e89bb9c532b641abcf1b24c3896ab Mon Sep 17 00:00:00 2001 +From: lars4839 +Date: Mon, 26 Mar 2018 20:33:19 -0500 +Subject: [PATCH] Switch to Retry.remove_headers_on_redirect + +- Default to Authorization header. +- Allow different settings on the Retry object. +- Remove logic from ConnectionPool. +--- + CHANGES.rst | 6 ++-- + test/appengine/test_gae_manager.py | 4 --- + test/test_retry.py | 10 ++++++ + test/with_dummyserver/test_connectionpool.py | 36 +------------------- + test/with_dummyserver/test_poolmanager.py | 31 +++++++++++++---- + urllib3/connectionpool.py | 7 ---- + urllib3/poolmanager.py | 10 +++--- + urllib3/util/retry.py | 19 ++++++----- + 8 files changed, 54 insertions(+), 69 deletions(-) + +#diff --git a/CHANGES.rst b/CHANGES.rst +#index 35334c8c4..3e13c59ac 100644 +#--- a/CHANGES.rst +#+++ b/CHANGES.rst +#@@ -18,9 +18,9 @@ dev (master) +# +# * Lazily load `uuid` to boost performance on imports (Pull #1270) +# +#-* Stop forwarding the ``Authentication`` HTTP header on a cross-host +#- redirect unless disabled by setting ``forward_auth_headers_across_hosts`` +#- to True on ``Retry`` objects. (Issue #1316) +#+* Allow providing a list of headers to strip from requests when redirecting +#+ to a different host. Defaults to the ``Authorization`` header. Different +#+ headers can be set via ``Retry.remove_headers_on_redirect``. (Issue #1316) +# +# * ... [Short description of non-trivial change.] (Issue #) +# +Index: python-urllib3-1.13.1/test/test_retry.py +=================================================================== +--- python-urllib3-1.13.1.orig/test/test_retry.py 2019-05-13 15:17:06.773474079 -0400 ++++ python-urllib3-1.13.1/test/test_retry.py 2019-05-13 15:18:21.337749938 -0400 +@@ -196,3 +196,13 @@ class RetryTest(unittest.TestCase): + except MaxRetryError as e: + assert 'Caused by redirect' not in str(e) + self.assertEqual(str(e.reason), 'conntimeout') ++ ++ def test_retry_default_remove_headers_on_redirect(self): ++ retry = Retry() ++ ++ 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'] +Index: python-urllib3-1.13.1/test/with_dummyserver/test_connectionpool.py +=================================================================== +--- python-urllib3-1.13.1.orig/test/with_dummyserver/test_connectionpool.py 2019-05-13 15:17:06.773474079 -0400 ++++ python-urllib3-1.13.1/test/with_dummyserver/test_connectionpool.py 2019-05-13 15:18:48.961852788 -0400 +@@ -5,7 +5,6 @@ import sys + import unittest + import time + import warnings +-import json + + import mock + +@@ -16,9 +15,7 @@ except: + + from .. import ( + requires_network, onlyPy3, onlyPy26OrOlder, +- TARPIT_HOST, +- VALID_SOURCE_ADDRESSES, +- INVALID_SOURCE_ADDRESSES ++ TARPIT_HOST, VALID_SOURCE_ADDRESSES, INVALID_SOURCE_ADDRESSES, + ) + from ..port_helpers import find_unused_port + from urllib3 import ( +@@ -695,8 +692,6 @@ class TestRetry(HTTPDummyServerTestCase) + def setUp(self): + self.pool = HTTPConnectionPool(self.host, self.port) + +- self.base_url_alt = 'http://%s:%d' % (self.host_alt, self.port) +- + def test_max_retry(self): + try: + r = self.pool.request('GET', '/redirect', +@@ -779,35 +774,6 @@ class TestRetry(HTTPDummyServerTestCase) + headers=headers, retries=retry) + self.assertEqual(resp.status, 200) + +- def test_redirect_cross_host_no_forward_auth_headers(self): +- url = '/redirect?target=%s/headers' % self.base_url_alt +- headers = {'Authentication': 'foo'} +- +- resp = self.pool.urlopen('GET', url, headers=headers, assert_same_host=False) +- +- self.assertEqual(resp.status, 200) +- +- data = json.loads(resp.data.decode('utf-8')) +- +- self.assertNotIn('Authentication', data) +- +- def test_redirect_cross_host_forward_auth_headers(self): +- url = '/redirect?target=%s/headers' % self.base_url_alt +- headers = {'Authentication': 'foo'} +- retry = Retry(redirect=2, forward_auth_headers_across_hosts=True) +- +- resp = self.pool.urlopen( +- 'GET', url, headers=headers, +- assert_same_host=False, +- retries=retry +- ) +- +- self.assertEqual(resp.status, 200) +- +- data = json.loads(resp.data.decode('utf-8')) +- +- self.assertEqual(data['Authentication'], 'foo') +- + + if __name__ == '__main__': + unittest.main() +Index: python-urllib3-1.13.1/test/with_dummyserver/test_poolmanager.py +=================================================================== +--- python-urllib3-1.13.1.orig/test/with_dummyserver/test_poolmanager.py 2019-05-13 15:17:06.773474079 -0400 ++++ python-urllib3-1.13.1/test/with_dummyserver/test_poolmanager.py 2019-05-13 15:17:06.773474079 -0400 +@@ -100,34 +100,51 @@ class TestPoolManager(HTTPDummyServerTes + except MaxRetryError: + pass + +- def test_redirect_cross_host_strip_auth_headers(self): ++ def test_redirect_cross_host_remove_headers(self): + http = PoolManager() + self.addCleanup(http.clear) + + r = http.request('GET', '%s/redirect' % self.base_url, + fields={'target': '%s/headers' % self.base_url_alt}, +- headers={'Authentication': 'foo'}) ++ headers={'Authorization': 'foo'}) + + self.assertEqual(r.status, 200) + + data = json.loads(r.data.decode('utf-8')) + +- self.assertNotIn('Authentication', data) ++ self.assertNotIn('Authorization', data) + +- def test_redirect_cross_host_forward_auth_headers(self): ++ def test_redirect_cross_host_no_remove_headers(self): + http = PoolManager() + self.addCleanup(http.clear) + + r = http.request('GET', '%s/redirect' % self.base_url, + fields={'target': '%s/headers' % self.base_url_alt}, +- headers={'Authentication': 'foo'}, +- retries=Retry(forward_auth_headers_across_hosts=True)) ++ headers={'Authorization': 'foo'}, ++ retries=Retry(remove_headers_on_redirect=[])) + + self.assertEqual(r.status, 200) + + data = json.loads(r.data.decode('utf-8')) + +- self.assertEqual(data['Authentication'], 'foo') ++ self.assertEqual(data['Authorization'], 'foo') ++ ++ def test_redirect_cross_host_set_removed_headers(self): ++ http = PoolManager() ++ self.addCleanup(http.clear) ++ ++ 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.assertEqual(data['Authorization'], 'bar') + + def test_raise_on_redirect(self): + http = PoolManager() +Index: python-urllib3-1.13.1/urllib3/connectionpool.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/connectionpool.py 2019-05-13 15:17:06.773474079 -0400 ++++ python-urllib3-1.13.1/urllib3/connectionpool.py 2019-05-13 15:19:02.677903976 -0400 +@@ -645,13 +645,6 @@ class HTTPConnectionPool(ConnectionPool, + raise + return response + +- # Strip the Authentication header if redirecting to a new host +- # and we don't want to forward that header across hosts. +- if (not retries.forward_auth_headers_across_hosts +- and 'Authentication' in headers +- and not self.is_same_host(redirect_location)): +- headers.pop('Authentication') +- + log.info("Redirecting %s -> %s" % (url, redirect_location)) + return self.urlopen( + method, redirect_location, body, headers, +Index: python-urllib3-1.13.1/urllib3/poolmanager.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/poolmanager.py 2019-05-13 15:17:06.773474079 -0400 ++++ python-urllib3-1.13.1/urllib3/poolmanager.py 2019-05-13 15:17:06.773474079 -0400 +@@ -179,12 +179,12 @@ class PoolManager(RequestMethods): + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + +- # Strip the Authentication header if redirecting to a new host +- # and we don't want to forward that header across hosts. +- if (not retries.forward_auth_headers_across_hosts +- and 'Authentication' in headers ++ # Strip headers marked as unsafe to forward to the redirected location. ++ if (retries.remove_headers_on_redirect + and not conn.is_same_host(redirect_location)): +- headers.pop('Authentication') ++ for header in retries.remove_headers_on_redirect: ++ if header in headers: ++ headers.pop(header) + + try: + retries = retries.increment(method, url, response=response, _pool=conn) +Index: python-urllib3-1.13.1/urllib3/util/retry.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/util/retry.py 2019-05-13 15:17:06.773474079 -0400 ++++ python-urllib3-1.13.1/urllib3/util/retry.py 2019-05-13 15:19:52.042088832 -0400 +@@ -103,10 +103,10 @@ class Retry(object): + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + +- :param bool forward_auth_headers_across_hosts: +- Whether to forward Authentication headers if a response is received +- that redirects to a different host than the original request. +- Defaults to False. ++ :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. + """ + + DEFAULT_METHOD_WHITELIST = frozenset([ +@@ -118,7 +118,7 @@ class Retry(object): + 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, _observed_errors=0, +- forward_auth_headers_across_hosts=False): ++ remove_headers_on_redirect=None): + + self.total = total + self.connect = connect +@@ -128,14 +128,17 @@ class Retry(object): + redirect = 0 + raise_on_redirect = False + ++ if remove_headers_on_redirect is None: ++ remove_headers_on_redirect = ['Authorization'] ++ remove_headers_on_redirect = set(remove_headers_on_redirect) ++ + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.method_whitelist = method_whitelist + self.backoff_factor = backoff_factor + self.raise_on_redirect = raise_on_redirect + self._observed_errors = _observed_errors # TODO: use .history instead? +- self.forward_auth_headers_across_hosts = \ +- forward_auth_headers_across_hosts ++ self.remove_headers_on_redirect = remove_headers_on_redirect + + def new(self, **kw): + params = dict( +@@ -146,7 +149,7 @@ class Retry(object): + backoff_factor=self.backoff_factor, + raise_on_redirect=self.raise_on_redirect, + _observed_errors=self._observed_errors, +- forward_auth_headers_across_hosts=self.forward_auth_headers_across_hosts ++ remove_headers_on_redirect=self.remove_headers_on_redirect + ) + params.update(kw) + return type(self)(**params) diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2018-20060-7.patch python-urllib3-1.13.1/debian/patches/CVE-2018-20060-7.patch --- python-urllib3-1.13.1/debian/patches/CVE-2018-20060-7.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2018-20060-7.patch 2019-05-13 19:20:57.000000000 +0000 @@ -0,0 +1,42 @@ +From 6245ddddb7f80740c5c15e1750e5b9f68c5b2b5f Mon Sep 17 00:00:00 2001 +From: "Seth M. Larson" +Date: Tue, 27 Mar 2018 13:14:11 -0500 +Subject: [PATCH] Stop using headers, instead use kw['headers'] + +--- + urllib3/poolmanager.py | 8 ++------ + 1 file changed, 2 insertions(+), 6 deletions(-) + +Index: python-urllib3-1.13.1/urllib3/poolmanager.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/poolmanager.py 2019-05-13 15:20:52.830317714 -0400 ++++ python-urllib3-1.13.1/urllib3/poolmanager.py 2019-05-13 15:20:52.830317714 -0400 +@@ -155,9 +155,7 @@ class PoolManager(RequestMethods): + kw['redirect'] = False + + if 'headers' not in kw: +- kw['headers'] = headers = self.headers +- else: +- headers = kw['headers'] ++ kw['headers'] = self.headers.copy() + + if self.proxy is not None and u.scheme == "http": + response = conn.urlopen(method, url, **kw) +@@ -183,8 +181,7 @@ class PoolManager(RequestMethods): + if (retries.remove_headers_on_redirect + and not conn.is_same_host(redirect_location)): + for header in retries.remove_headers_on_redirect: +- if header in headers: +- headers.pop(header) ++ kw['headers'].pop(header, None) + + try: + retries = retries.increment(method, url, response=response, _pool=conn) +@@ -195,7 +192,6 @@ class PoolManager(RequestMethods): + + kw['retries'] = retries + kw['redirect'] = redirect +- kw['headers'] = headers + + log.info("Redirecting %s -> %s" % (url, redirect_location)) + return self.urlopen(method, redirect_location, **kw) diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2018-20060-8.patch python-urllib3-1.13.1/debian/patches/CVE-2018-20060-8.patch --- python-urllib3-1.13.1/debian/patches/CVE-2018-20060-8.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2018-20060-8.patch 2019-05-13 19:22:04.000000000 +0000 @@ -0,0 +1,33 @@ +Backport of: + +From 3b5f27449e153ad05186beca8fbd9b134936fe50 Mon Sep 17 00:00:00 2001 +From: "Seth M. Larson" +Date: Tue, 27 Mar 2018 14:35:58 -0500 +Subject: [PATCH] Add a default list of headers to strip as a global + +--- + urllib3/util/retry.py | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +Index: python-urllib3-1.13.1/urllib3/util/retry.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/util/retry.py 2019-05-13 15:21:20.678422989 -0400 ++++ python-urllib3-1.13.1/urllib3/util/retry.py 2019-05-13 15:21:42.798506784 -0400 +@@ -14,6 +14,8 @@ import six + + log = logging.getLogger(__name__) + ++DEFAULT_REDIRECT_HEADERS_BLACKLIST = ['Authorization'] ++ + + class Retry(object): + """ Retry configuration. +@@ -129,7 +131,7 @@ class Retry(object): + raise_on_redirect = False + + if remove_headers_on_redirect is None: +- remove_headers_on_redirect = ['Authorization'] ++ remove_headers_on_redirect = DEFAULT_REDIRECT_HEADERS_BLACKLIST + remove_headers_on_redirect = set(remove_headers_on_redirect) + + self.redirect = redirect diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2018-20060-9.patch python-urllib3-1.13.1/debian/patches/CVE-2018-20060-9.patch --- python-urllib3-1.13.1/debian/patches/CVE-2018-20060-9.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2018-20060-9.patch 2019-05-13 19:22:14.000000000 +0000 @@ -0,0 +1,22 @@ +From 1742538d57865e61125c6c12a755b5db41636fe7 Mon Sep 17 00:00:00 2001 +From: "Seth M. Larson" +Date: Tue, 27 Mar 2018 14:40:38 -0500 +Subject: [PATCH] Add a comment about short-circuiting is_same_host + +--- + urllib3/poolmanager.py | 2 ++ + 1 file changed, 2 insertions(+) + +Index: python-urllib3-1.13.1/urllib3/poolmanager.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/poolmanager.py 2019-05-13 15:22:11.982617564 -0400 ++++ python-urllib3-1.13.1/urllib3/poolmanager.py 2019-05-13 15:22:11.982617564 -0400 +@@ -178,6 +178,8 @@ class PoolManager(RequestMethods): + 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: diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2019-11236-1.patch python-urllib3-1.13.1/debian/patches/CVE-2019-11236-1.patch --- python-urllib3-1.13.1/debian/patches/CVE-2019-11236-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2019-11236-1.patch 2019-05-14 13:00:12.000000000 +0000 @@ -0,0 +1,95 @@ +Backport of: + +From 9b76785331243689a9d52cef3db05ef7462cb02d Mon Sep 17 00:00:00 2001 +From: Ryan Petrello +Date: Wed, 1 May 2019 13:11:20 -0400 +Subject: [PATCH] Apply fix from CPython for CVE-2019-9740 (#1591) + +--- + src/urllib3/connection.py | 2 +- + src/urllib3/connectionpool.py | 2 +- + src/urllib3/contrib/pyopenssl.py | 4 ++-- + src/urllib3/contrib/socks.py | 2 +- + src/urllib3/poolmanager.py | 2 +- + src/urllib3/response.py | 4 +++- + src/urllib3/util/ssl_.py | 2 -- + src/urllib3/util/url.py | 8 +++++++ + test/__init__.py | 29 +++++++++++++++++++++++ + test/test_util.py | 5 ++++ + test/with_dummyserver/test_https.py | 4 ++++ + test/with_dummyserver/test_socketlevel.py | 2 ++ + tox.ini | 2 +- + 13 files changed, 58 insertions(+), 10 deletions(-) + +Index: python-urllib3-1.13.1/urllib3/connection.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/connection.py 2019-05-14 08:58:38.962239337 -0400 ++++ python-urllib3-1.13.1/urllib3/connection.py 2019-05-14 08:58:38.962239337 -0400 +@@ -136,7 +136,7 @@ class HTTPConnection(_HTTPConnection, ob + conn = connection.create_connection( + (self.host, self.port), self.timeout, **extra_kw) + +- except SocketTimeout as e: ++ except SocketTimeout: + raise ConnectTimeoutError( + self, "Connection to %s timed out. (connect timeout=%s)" % + (self.host, self.timeout)) +Index: python-urllib3-1.13.1/urllib3/contrib/pyopenssl.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/contrib/pyopenssl.py 2019-05-14 08:58:38.962239337 -0400 ++++ python-urllib3-1.13.1/urllib3/contrib/pyopenssl.py 2019-05-14 08:58:38.962239337 -0400 +@@ -183,7 +183,7 @@ class WrappedSocket(object): + return b'' + else: + raise SocketError(e) +- except OpenSSL.SSL.ZeroReturnError as e: ++ except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b'' + else: +Index: python-urllib3-1.13.1/urllib3/util/url.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/util/url.py 2019-05-14 08:58:38.962239337 -0400 ++++ python-urllib3-1.13.1/urllib3/util/url.py 2019-05-14 08:58:38.962239337 -0400 +@@ -1,11 +1,14 @@ + from __future__ import absolute_import + from collections import namedtuple ++import re + + from ..exceptions import LocationParseError + + + url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] + ++_contains_disallowed_url_pchar_re = re.compile('[\x00-\x20\x7f]') ++ + + class Url(namedtuple('Url', url_attrs)): + """ +@@ -146,6 +149,11 @@ def parse_url(url): + # Empty + return Url() + ++ # Prevent CVE-2019-9740. ++ # adapted from https://github.com/python/cpython/pull/12755 ++ if _contains_disallowed_url_pchar_re.search(url): ++ raise LocationParseError("URL can't contain control characters. {!r}".format(url)) ++ + scheme = None + auth = None + host = None +Index: python-urllib3-1.13.1/test/test_util.py +=================================================================== +--- python-urllib3-1.13.1.orig/test/test_util.py 2019-05-14 08:58:38.962239337 -0400 ++++ python-urllib3-1.13.1/test/test_util.py 2019-05-14 09:00:06.383049031 -0400 +@@ -149,6 +149,10 @@ class TestUtil(unittest.TestCase): + def test_parse_url_invalid_IPv6(self): + self.assertRaises(ValueError, parse_url, '[::1') + ++ def test_parse_url_contains_control_characters(self): ++ # see CVE-2019-9740 ++ self.assertRaises(LocationParseError, parse_url, 'http://localhost:8000/ HTTP/1.1\r\nHEADER: INJECTED\r\nIgnore:') ++ + def test_Url_str(self): + U = Url('http', host='google.com') + self.assertEqual(str(U), U.url) diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2019-11236-2.patch python-urllib3-1.13.1/debian/patches/CVE-2019-11236-2.patch --- python-urllib3-1.13.1/debian/patches/CVE-2019-11236-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2019-11236-2.patch 2019-05-14 13:05:30.000000000 +0000 @@ -0,0 +1,64 @@ +Backport of: + +From efddd7e7bad26188c3b692d1090cba768afa9162 Mon Sep 17 00:00:00 2001 +From: Ryan Petrello +Date: Thu, 2 May 2019 11:03:09 -0400 +Subject: [PATCH] Avoid CVE-2019-9740 in 1.24.x by percent-encoding invalid + target characters (#1593) + +--- + src/urllib3/util/url.py | 4 ++-- + test/test_util.py | 22 +++++++++++++++++++--- + 2 files changed, 21 insertions(+), 5 deletions(-) + +Index: python-urllib3-1.13.1/urllib3/util/url.py +=================================================================== +--- python-urllib3-1.13.1.orig/urllib3/util/url.py 2019-05-14 09:00:24.999214311 -0400 ++++ python-urllib3-1.13.1/urllib3/util/url.py 2019-05-14 09:00:24.999214311 -0400 +@@ -3,6 +3,7 @@ from collections import namedtuple + import re + + from ..exceptions import LocationParseError ++from ..packages.six.moves.urllib.parse import quote + + + url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] +@@ -151,8 +152,7 @@ def parse_url(url): + + # Prevent CVE-2019-9740. + # adapted from https://github.com/python/cpython/pull/12755 +- if _contains_disallowed_url_pchar_re.search(url): +- raise LocationParseError("URL can't contain control characters. {!r}".format(url)) ++ url = _contains_disallowed_url_pchar_re.sub(lambda match: quote(match.group()), url) + + scheme = None + auth = None +Index: python-urllib3-1.13.1/test/test_util.py +=================================================================== +--- python-urllib3-1.13.1.orig/test/test_util.py 2019-05-14 09:00:24.999214311 -0400 ++++ python-urllib3-1.13.1/test/test_util.py 2019-05-14 09:05:20.889579775 -0400 +@@ -149,9 +149,22 @@ class TestUtil(unittest.TestCase): + def test_parse_url_invalid_IPv6(self): + self.assertRaises(ValueError, parse_url, '[::1') + +- def test_parse_url_contains_control_characters(self): ++ def test_parse_url_contains_control_characters_1(self): + # see CVE-2019-9740 +- self.assertRaises(LocationParseError, parse_url, 'http://localhost:8000/ HTTP/1.1\r\nHEADER: INJECTED\r\nIgnore:') ++ url = parse_url('http://localhost/ HTTP/1.1\r\nHEADER: INJECTED\r\nIgnore:') ++ assert url == Url('http', host='localhost', port=None, ++ path='/%20HTTP/1.1%0D%0AHEADER:%20INJECTED%0D%0AIgnore:') ++ ++ def test_parse_url_contains_control_characters_2(self): ++ # see CVE-2019-9740 ++ url = parse_url(u'http://localhost/ HTTP/1.1\r\nHEADER: INJECTED\r\nIgnore:') ++ assert url == Url('http', host='localhost', port=None, ++ path='/%20HTTP/1.1%0D%0AHEADER:%20INJECTED%0D%0AIgnore:') ++ ++ def test_parse_url_contains_control_characters_3(self): ++ # see CVE-2019-9740 ++ url = parse_url('http://localhost/ ?q=\r\n') ++ assert url == Url('http', host='localhost', path='/%20', query='q=%0D%0A') + + def test_Url_str(self): + U = Url('http', host='google.com') diff -Nru python-urllib3-1.13.1/debian/patches/CVE-2019-11236-3.patch python-urllib3-1.13.1/debian/patches/CVE-2019-11236-3.patch --- python-urllib3-1.13.1/debian/patches/CVE-2019-11236-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/CVE-2019-11236-3.patch 2019-05-13 19:26:01.000000000 +0000 @@ -0,0 +1,17 @@ +Description: don't use embedded python-six +Author: Marc Deslauriers +Forwarded: Not needed. Specific to Debian packaging. + +Index: python-urllib3-1.24.1/urllib3/util/url.py +=================================================================== +--- python-urllib3-1.24.1.orig/urllib3/util/url.py 2019-05-13 13:44:41.509708019 -0400 ++++ python-urllib3-1.24.1/urllib3/util/url.py 2019-05-13 13:45:07.617788063 -0400 +@@ -3,7 +3,7 @@ from collections import namedtuple + import re + + from ..exceptions import LocationParseError +-from ..packages.six.moves.urllib.parse import quote ++from six.moves.urllib.parse import quote + + + url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] diff -Nru python-urllib3-1.13.1/debian/patches/series python-urllib3-1.13.1/debian/patches/series --- python-urllib3-1.13.1/debian/patches/series 2018-08-20 14:49:03.000000000 +0000 +++ python-urllib3-1.13.1/debian/patches/series 2019-05-13 19:26:01.000000000 +0000 @@ -5,3 +5,16 @@ 05_avoid-embedded-ssl-match-hostname.patch 06_revert_square_brackets_httplib.patch 07_support_ip_sans.patch +CVE-2018-20060-1.patch +CVE-2018-20060-2.patch +CVE-2018-20060-3.patch +CVE-2018-20060-5.patch +CVE-2018-20060-6.patch +CVE-2018-20060-7.patch +CVE-2018-20060-8.patch +CVE-2018-20060-9.patch +CVE-2018-20060-10.patch +CVE-2018-20060-12.patch +CVE-2019-11236-1.patch +CVE-2019-11236-2.patch +CVE-2019-11236-3.patch