diff --git a/SOURCES/CVE-2018-20060.patch b/SOURCES/CVE-2018-20060.patch new file mode 100644 index 0000000..f9e6c60 --- /dev/null +++ b/SOURCES/CVE-2018-20060.patch @@ -0,0 +1,108 @@ +From 73474e1b518ce91481b7101d68d8a75c7ad67029 Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Mon, 4 Mar 2019 12:45:09 +0100 +Subject: [PATCH] CVE-2018-20060 + +Cross-host redirect does not remove Authorization header allow +for credential exposure. +--- + CHANGES.rst | 4 ++++ + urllib3/poolmanager.py | 11 ++++++++++- + urllib3/util/retry.py | 12 +++++++++++- + 3 files changed, 25 insertions(+), 2 deletions(-) + +diff --git a/CHANGES.rst b/CHANGES.rst +index 0150d85..34ad6b0 100644 +--- a/CHANGES.rst ++++ b/CHANGES.rst +@@ -1,6 +1,10 @@ + Changes + ======= + ++* 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) ++ + * Accept ``iPAddress`` subject alternative name fields in TLS certificates. + (Issue #258) + +diff --git a/urllib3/poolmanager.py b/urllib3/poolmanager.py +index 4fdae8d..b43b235 100644 +--- a/urllib3/poolmanager.py ++++ b/urllib3/poolmanager.py +@@ -238,8 +238,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) +@@ -261,6 +262,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/urllib3/util/retry.py b/urllib3/util/retry.py +index 7e0959d..0cf8eed 100644 +--- a/urllib3/util/retry.py ++++ b/urllib3/util/retry.py +@@ -101,17 +101,25 @@ 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 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([ + '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, _observed_errors=0): ++ backoff_factor=0, raise_on_redirect=True, _observed_errors=0, ++ remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST): + + self.total = total + self.connect = connect +@@ -127,6 +135,7 @@ 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.remove_headers_on_redirect = remove_headers_on_redirect + + def new(self, **kw): + params = dict( +@@ -137,6 +146,7 @@ class Retry(object): + backoff_factor=self.backoff_factor, + raise_on_redirect=self.raise_on_redirect, + _observed_errors=self._observed_errors, ++ remove_headers_on_redirect=self.remove_headers_on_redirect, + ) + params.update(kw) + return type(self)(**params) +-- +2.20.1 + diff --git a/SOURCES/CVE-2019-9740.patch b/SOURCES/CVE-2019-9740.patch new file mode 100644 index 0000000..8583077 --- /dev/null +++ b/SOURCES/CVE-2019-9740.patch @@ -0,0 +1,107 @@ +From 374bf2ea399cd1f2abbc0890fe796d570416adea Mon Sep 17 00:00:00 2001 +From: Ryan Petrello +Date: Tue, 30 Apr 2019 12:36:48 -0400 +Subject: [PATCH 1/2] prevent CVE-2019-9740 in 1.24.x + +adapted from https://github.com/python/cpython/pull/12755 +--- + test/test_util.py | 5 +++++ + urllib3/util/url.py | 8 ++++++++ + 2 files changed, 13 insertions(+) + +diff --git a/test/test_util.py b/test/test_util.py +index c850d91..5fa49bb 100644 +--- a/test/test_util.py ++++ b/test/test_util.py +@@ -142,6 +142,11 @@ 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 ++ with 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 --git a/urllib3/util/url.py b/urllib3/util/url.py +index b2ec834..7878892 100644 +--- a/urllib3/util/url.py ++++ b/urllib3/util/url.py +@@ -1,10 +1,13 @@ + 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)): + """ +@@ -142,6 +145,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 + +From 5661da387242337e09a939120a1e154e7335d18b Mon Sep 17 00:00:00 2001 +From: Ryan Petrello +Date: Wed, 1 May 2019 16:46:44 -0400 +Subject: [PATCH 2/2] avoid CVE-2019-9740 by percent-encoding invalid path + characters + +this is to avoid breaking changes in downstream libraries like requests +--- + test/test_util.py | 4 ++-- + urllib3/util/url.py | 4 ++-- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/test/test_util.py b/test/test_util.py +index 5fa49bb..122e00b 100644 +--- a/test/test_util.py ++++ b/test/test_util.py +@@ -144,8 +144,8 @@ class TestUtil(unittest.TestCase): + + def test_parse_url_contains_control_characters(self): + # see CVE-2019-9740 +- with self.assertRaises(LocationParseError): +- parse_url('http://localhost:8000/ HTTP/1.1\r\nHEADER: INJECTED\r\nIgnore:') ++ url = parse_url('http://localhost:8000/ HTTP/1.1\r\nHEADER: INJECTED\r\nIgnore:') ++ self.assertEqual(url.path, '/%20HTTP/1.1%0D%0AHEADER:%20INJECTED%0D%0AIgnore:') + + def test_Url_str(self): + U = Url('http', host='google.com') +diff --git a/urllib3/util/url.py b/urllib3/util/url.py +index 7878892..cc15b6b 100644 +--- a/urllib3/util/url.py ++++ b/urllib3/util/url.py +@@ -2,6 +2,7 @@ from collections import namedtuple + import re + + from ..exceptions import LocationParseError ++from six.moves.urllib.parse import quote + + + url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] +@@ -147,8 +148,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 diff --git a/SPECS/python-urllib3.spec b/SPECS/python-urllib3.spec index 75be1ed..3104ec2 100644 --- a/SPECS/python-urllib3.spec +++ b/SPECS/python-urllib3.spec @@ -1,19 +1,19 @@ -%if 0%{?fedora} -%global with_python3 1 -%else -%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print (get_python_lib())")} -%endif +# Tests are EPEL only +%bcond_with tests + +# No Python 3 by default +%bcond_with python3 %global srcname urllib3 Name: python-%{srcname} Version: 1.10.2 -Release: 5%{?dist} +Release: 7%{?dist} Summary: Python HTTP library with thread-safe connection pooling and file post License: MIT URL: http://urllib3.readthedocs.org/ -Source0: http://pypi.python.org/packages/source/u/%{srcname}/%{srcname}-%{version}.tar.gz +Source0: https://pypi.python.org/packages/source/u/%{srcname}/%{srcname}-%{version}.tar.gz # Patch to change default behaviour to check SSL certs for validity # https://bugzilla.redhat.com/show_bug.cgi?id=855320 @@ -30,6 +30,22 @@ Patch1: key-connection-pools-off-custom-keys.patch # Upstream: https://github.com/shazow/urllib3/pull/922 Patch2: Add-support-for-IP-address-SAN-fields.patch +# Patch for CVE-2018-20060 +# Cross-host redirect does not remove Authorization header allow +# for credential exposure +# Backported without tests! +# https://bugzilla.redhat.com/show_bug.cgi?id=1649153 +# Upstream: https://github.com/urllib3/urllib3/pull/1346 +Patch3: CVE-2018-20060.patch + +# Patch for CVE-2019-9740 +# CRLF injection due to not encoding the '\r\n' sequence leading to possible +# attack on internal service. +# https://github.com/urllib3/urllib3/pull/1591/commits/c147f359520cab339ec96b3ef96e471c0da261f6 +# https://github.com/urllib3/urllib3/pull/1593/commits/e951dfc83a642b0b5239559cb1c8cc287481f1ae +# https://bugzilla.redhat.com/show_bug.cgi?id=1700824 +Patch4: CVE-2019-9740.patch + BuildArch: noarch Requires: ca-certificates @@ -50,30 +66,38 @@ BuildRequires: python-six BuildRequires: python-backports-ssl_match_hostname BuildRequires: python-ipaddress # For unittests -#BuildRequires: python-nose -#BuildRequires: python-tornado -#BuildRequires: python-mock +%if %{with tests} +BuildRequires: python-nose +BuildRequires: python-tornado +BuildRequires: python-mock +%endif -%if 0%{?with_python3} -BuildRequires: python3-devel -# For unittests -BuildRequires: python3-nose -BuildRequires: python3-six -BuildRequires: python3-tornado -%endif # with_python3 +%{?python_provide:%python_provide %{name}} %description Python HTTP module with connection pooling and file POST abilities. -%if 0%{?with_python3} + +%if %{with python3} %package -n python3-%{srcname} +Summary: Python 3 HTTP library with thread-safe connection pooling and file post +%{?python_provide:%python_provide python3-%{srcname}} + Requires: ca-certificates Requires: python3-six -# Note: Will not run with python3 < 3.2 (unless python3-backports-ssl_match_hostname is created) -Summary: Python3 HTTP library with thread-safe connection pooling and file post +BuildRequires: python3-devel + +# For unittests +%if %{with tests} +BuildRequires: python3-nose +BuildRequires: python3-six +BuildRequires: python3-tornado +BuildRequires: python3-mock +%endif # with tests + %description -n python3-%{srcname} Python3 HTTP module with connection pooling and file POST abilities. -%endif # with_python3 +%endif # with python3 %prep @@ -86,70 +110,73 @@ rm -rf test/with_dummyserver/ %patch0 -p1 %patch1 -p1 %patch2 -p1 +%patch3 -p1 +%patch4 -p1 -%if 0%{?with_python3} -rm -rf %{py3dir} -cp -a . %{py3dir} -%endif # with_python3 %build -%{__python} setup.py build +%py2_build -%if 0%{?with_python3} -pushd %{py3dir} -%{__python3} setup.py build -popd -%endif # with_python3 +%if %{with python3} +%py3_build +%endif %install -rm -rf %{buildroot} -%{__python} setup.py install --skip-build --root %{buildroot} +%py2_install -rm -rf %{buildroot}/%{python_sitelib}/urllib3/packages/six.py* -rm -rf %{buildroot}/%{python_sitelib}/urllib3/packages/ssl_match_hostname/ +rm -rf %{buildroot}/%{python2_sitelib}/urllib3/packages/six.py* +rm -rf %{buildroot}/%{python2_sitelib}/urllib3/packages/ssl_match_hostname/ -mkdir -p %{buildroot}/%{python_sitelib}/urllib3/packages/ +mkdir -p %{buildroot}/%{python2_sitelib}/urllib3/packages/ # ovirt composes remove *.py files, leaving only *.pyc files there; this means we have to symlink # six.py* to make sure urllib3.packages.six will be importable for i in ../../six.py{,o,c}; do - ln -s $i %{buildroot}/%{python_sitelib}/urllib3/packages/ + ln -s $i %{buildroot}/%{python2_sitelib}/urllib3/packages/ done -ln -s ../../backports/ssl_match_hostname %{buildroot}/%{python_sitelib}/urllib3/packages/ssl_match_hostname +ln -s ../../backports/ssl_match_hostname %{buildroot}/%{python2_sitelib}/urllib3/packages/ssl_match_hostname # dummyserver is part of the unittest framework -rm -rf %{buildroot}%{python_sitelib}/dummyserver +rm -rf %{buildroot}%{python2_sitelib}/dummyserver -%if 0%{?with_python3} -pushd %{py3dir} -%{__python3} setup.py install --skip-build --root %{buildroot} +%if %{with python3} +%py3_install # dummyserver is part of the unittest framework rm -rf %{buildroot}%{python3_sitelib}/dummyserver -popd -%endif # with_python3 +%endif # with python3 -#%%check -#nosetests +%if %{with tests} +%check +nosetests-%{python2_version} -#%if 0%{?with_python3} -#pushd %{py3dir} -#nosetests-%{python3_version} -#popd -#%endif # with_python3 +%if %{with python3} +nosetests-%{python3_version} +%endif # with python3 +%endif # with tests %files -%doc CHANGES.rst LICENSE.txt README.rst CONTRIBUTORS.txt -# For noarch packages: sitelib -%{python_sitelib}/* +%doc CHANGES.rst README.rst CONTRIBUTORS.txt +%license LICENSE.txt +%{python2_sitelib}/urllib3* -%if 0%{?with_python3} +%if %{with python3} %files -n python3-%{srcname} -%doc LICENSE.txt -# For noarch packages: sitelib -%{python3_sitelib}/* -%endif # with_python3 +%doc CHANGES.rst README.rst CONTRIBUTORS.txt +%license LICENSE.txt +%{python3_sitelib}/urllib3* +%endif # with python3 %changelog +* Fri May 03 2019 Miro Hrončok - 1.10.2-7 +- Provide python2-urllib3 +- Add patch for CVE-2019-11236 +Resolves: rhbz#1703360 + +* Mon Mar 04 2019 Lumír Balhar - 1.10.2-6 +- Source URL switched to HTTPS protocol +- Add patch for CVE-2018-20060 +Resolves: rhbz#1658471 + * Wed Oct 11 2017 Iryna Shcherbina - 1.10.2-5 - Add patch to support IP address SAN fields. Resolves: rhbz#1434114