From 8614af620b4af2224fbcb259cfa8b0d59fd9804b Mon Sep 17 00:00:00 2001 From: shixuantong Date: Thu, 17 Feb 2022 20:17:22 +0800 Subject: [PATCH] fix CVE-2021-4189 CVE-2022-0391 --- backport-CVE-2021-4189.patch | 141 +++++++++++++++++++++++++++++ backport-CVE-2022-0391.patch | 170 +++++++++++++++++++++++++++++++++++ python3.spec | 12 ++- 3 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 backport-CVE-2021-4189.patch create mode 100644 backport-CVE-2022-0391.patch diff --git a/backport-CVE-2021-4189.patch b/backport-CVE-2021-4189.patch new file mode 100644 index 0000000..8278015 --- /dev/null +++ b/backport-CVE-2021-4189.patch @@ -0,0 +1,141 @@ +From 79373951b3eab585d42e0f0ab83718cbe1d0ee33 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 16 Mar 2021 14:19:55 -0700 +Subject: [PATCH] [3.7] bpo-43285 Make ftplib not trust the PASV response. + (GH-24838) (GH-24881) (GH-24883) + +The IPv4 address value returned from the server in response to the PASV command +should not be trusted. This prevents a malicious FTP server from using the +response to probe IPv4 address and port combinations on the client network. + +Instead of using the returned address, we use the IP address we're +already connected to. This is the strategy other ftp clients adopted, +and matches the only strategy available for the modern IPv6 EPSV command +where the server response must return a port number and nothing else. + +For the rare user who _wants_ this ugly behavior, set a `trust_server_pasv_ipv4_address` +attribute on your `ftplib.FTP` instance to True.. +(cherry picked from commit 0ab152c6b5d95caa2dc1a30fa96e10258b5f188e) + +Co-authored-by: Gregory P. Smith +(cherry picked from commit 664d1d16274b47eea6ec92572e1ebf3939a6fa0c) +--- + Doc/whatsnew/3.7.rst | 9 +++++++ + Lib/ftplib.py | 9 ++++++- + Lib/test/test_ftplib.py | 27 ++++++++++++++++++- + .../2021-03-13-03-48-14.bpo-43285.g-Hah3.rst | 8 ++++++ + 4 files changed, 51 insertions(+), 2 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2021-03-13-03-48-14.bpo-43285.g-Hah3.rst + +diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst +index 25e231dcc7dfa..85f924b2e41f3 100644 +--- a/Doc/whatsnew/3.7.rst ++++ b/Doc/whatsnew/3.7.rst +@@ -2585,3 +2585,12 @@ separator key, with ``&`` as the default. This change also affects + functions internally. For more details, please see their respective + documentation. + (Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.) ++ ++Notable changes in Python 3.7.11 ++================================ ++ ++A security fix alters the :class:`ftplib.FTP` behavior to not trust the ++IPv4 address sent from the remote server when setting up a passive data ++channel. We reuse the ftp server IP address instead. For unusual code ++requiring the old behavior, set a ``trust_server_pasv_ipv4_address`` ++attribute on your FTP instance to ``True``. (See :issue:`43285`) +diff --git a/Lib/ftplib.py b/Lib/ftplib.py +index 9611282ecacb2..83ed0aa9c140e 100644 +--- a/Lib/ftplib.py ++++ b/Lib/ftplib.py +@@ -104,6 +104,8 @@ class FTP: + welcome = None + passiveserver = 1 + encoding = "latin-1" ++ # Disables https://bugs.python.org/issue43285 security if set to True. ++ trust_server_pasv_ipv4_address = False + + # Initialization method (called by class instantiation). + # Initialize host to localhost, port to standard ftp port +@@ -333,8 +335,13 @@ def makeport(self): + return sock + + def makepasv(self): ++ """Internal: Does the PASV or EPSV handshake -> (address, port)""" + if self.af == socket.AF_INET: +- host, port = parse227(self.sendcmd('PASV')) ++ untrusted_host, port = parse227(self.sendcmd('PASV')) ++ if self.trust_server_pasv_ipv4_address: ++ host = untrusted_host ++ else: ++ host = self.sock.getpeername()[0] + else: + host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername()) + return host, port +diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py +index da8ba32917be7..73a6250a5e4ae 100644 +--- a/Lib/test/test_ftplib.py ++++ b/Lib/test/test_ftplib.py +@@ -94,6 +94,10 @@ def __init__(self, conn): + self.rest = None + self.next_retr_data = RETR_DATA + self.push('220 welcome') ++ # We use this as the string IPv4 address to direct the client ++ # to in response to a PASV command. To test security behavior. ++ # https://bugs.python.org/issue43285/. ++ self.fake_pasv_server_ip = '252.253.254.255' + + def collect_incoming_data(self, data): + self.in_buffer.append(data) +@@ -136,7 +140,8 @@ def cmd_pasv(self, arg): + sock.bind((self.socket.getsockname()[0], 0)) + sock.listen() + sock.settimeout(TIMEOUT) +- ip, port = sock.getsockname()[:2] ++ port = sock.getsockname()[1] ++ ip = self.fake_pasv_server_ip + ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256 + self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2)) + conn, addr = sock.accept() +@@ -698,6 +703,26 @@ def test_makepasv(self): + # IPv4 is in use, just make sure send_epsv has not been used + self.assertEqual(self.server.handler_instance.last_received_cmd, 'pasv') + ++ def test_makepasv_issue43285_security_disabled(self): ++ """Test the opt-in to the old vulnerable behavior.""" ++ self.client.trust_server_pasv_ipv4_address = True ++ bad_host, port = self.client.makepasv() ++ self.assertEqual( ++ bad_host, self.server.handler_instance.fake_pasv_server_ip) ++ # Opening and closing a connection keeps the dummy server happy ++ # instead of timing out on accept. ++ socket.create_connection((self.client.sock.getpeername()[0], port), ++ timeout=TIMEOUT).close() ++ ++ def test_makepasv_issue43285_security_enabled_default(self): ++ self.assertFalse(self.client.trust_server_pasv_ipv4_address) ++ trusted_host, port = self.client.makepasv() ++ self.assertNotEqual( ++ trusted_host, self.server.handler_instance.fake_pasv_server_ip) ++ # Opening and closing a connection keeps the dummy server happy ++ # instead of timing out on accept. ++ socket.create_connection((trusted_host, port), timeout=TIMEOUT).close() ++ + def test_with_statement(self): + self.client.quit() + +diff --git a/Misc/NEWS.d/next/Security/2021-03-13-03-48-14.bpo-43285.g-Hah3.rst b/Misc/NEWS.d/next/Security/2021-03-13-03-48-14.bpo-43285.g-Hah3.rst +new file mode 100644 +index 0000000000000..8312b7e885441 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2021-03-13-03-48-14.bpo-43285.g-Hah3.rst +@@ -0,0 +1,8 @@ ++:mod:`ftplib` no longer trusts the IP address value returned from the server ++in response to the PASV command by default. This prevents a malicious FTP ++server from using the response to probe IPv4 address and port combinations ++on the client network. ++ ++Code that requires the former vulnerable behavior may set a ++``trust_server_pasv_ipv4_address`` attribute on their ++:class:`ftplib.FTP` instances to ``True`` to re-enable it. diff --git a/backport-CVE-2022-0391.patch b/backport-CVE-2022-0391.patch new file mode 100644 index 0000000..547e2f3 --- /dev/null +++ b/backport-CVE-2022-0391.patch @@ -0,0 +1,170 @@ +From f4dac7ec55477a6c5d965e594e74bd6bda786903 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Thu, 6 May 2021 09:52:36 -0700 +Subject: [PATCH] [3.7] bpo-43882 - urllib.parse should sanitize urls + containing ASCII newline and tabs. (GH-25923) + +Co-authored-by: Gregory P. Smith +Co-authored-by: Serhiy Storchaka +(cherry picked from commit 76cd81d60310d65d01f9d7b48a8985d8ab89c8b4) +Co-authored-by: Senthil Kumaran +(cherry picked from commit 515a7bc4e13645d0945b46a8e1d9102b918cd407) + +Co-authored-by: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> +--- + Doc/library/urllib.parse.rst | 13 +++++ + Lib/test/test_urlparse.py | 48 +++++++++++++++++++ + Lib/urllib/parse.py | 10 ++++ + .../2021-04-25-07-46-37.bpo-43882.Jpwx85.rst | 6 +++ + 4 files changed, 77 insertions(+) + create mode 100644 Misc/NEWS.d/next/Security/2021-04-25-07-46-37.bpo-43882.Jpwx85.rst + +diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst +index e79faf035b06b..aea6505904be4 100644 +--- a/Doc/library/urllib.parse.rst ++++ b/Doc/library/urllib.parse.rst +@@ -311,6 +311,9 @@ or on combining URL components into a URL string. + ``#``, ``@``, or ``:`` will raise a :exc:`ValueError`. If the URL is + decomposed before parsing, no error will be raised. + ++ Following the `WHATWG spec`_ that updates RFC 3986, ASCII newline ++ ``\n``, ``\r`` and tab ``\t`` characters are stripped from the URL. ++ + .. versionchanged:: 3.6 + Out-of-range port numbers now raise :exc:`ValueError`, instead of + returning :const:`None`. +@@ -319,6 +322,10 @@ or on combining URL components into a URL string. + Characters that affect netloc parsing under NFKC normalization will + now raise :exc:`ValueError`. + ++ .. versionchanged:: 3.7.11 ++ ASCII newline and tab characters are stripped from the URL. ++ ++.. _WHATWG spec: https://url.spec.whatwg.org/#concept-basic-url-parser + + .. function:: urlunsplit(parts) + +@@ -660,6 +667,10 @@ task isn't already covered by the URL parsing functions above. + + .. seealso:: + ++ `WHATWG`_ - URL Living standard ++ Working Group for the URL Standard that defines URLs, domains, IP addresses, the ++ application/x-www-form-urlencoded format, and their API. ++ + :rfc:`3986` - Uniform Resource Identifiers + This is the current standard (STD66). Any changes to urllib.parse module + should conform to this. Certain deviations could be observed, which are +@@ -683,3 +694,5 @@ task isn't already covered by the URL parsing functions above. + + :rfc:`1738` - Uniform Resource Locators (URL) + This specifies the formal syntax and semantics of absolute URLs. ++ ++.. _WHATWG: https://url.spec.whatwg.org/ +diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py +index e3088b2f39bd7..3509278a01694 100644 +--- a/Lib/test/test_urlparse.py ++++ b/Lib/test/test_urlparse.py +@@ -612,6 +612,54 @@ def test_urlsplit_attributes(self): + with self.assertRaisesRegex(ValueError, "out of range"): + p.port + ++ def test_urlsplit_remove_unsafe_bytes(self): ++ # Remove ASCII tabs and newlines from input, for http common case scenario. ++ url = "h\nttp://www.python\n.org\t/java\nscript:\talert('msg\r\n')/?query\n=\tsomething#frag\nment" ++ p = urllib.parse.urlsplit(url) ++ self.assertEqual(p.scheme, "http") ++ self.assertEqual(p.netloc, "www.python.org") ++ self.assertEqual(p.path, "/javascript:alert('msg')/") ++ self.assertEqual(p.query, "query=something") ++ self.assertEqual(p.fragment, "fragment") ++ self.assertEqual(p.username, None) ++ self.assertEqual(p.password, None) ++ self.assertEqual(p.hostname, "www.python.org") ++ self.assertEqual(p.port, None) ++ self.assertEqual(p.geturl(), "http://www.python.org/javascript:alert('msg')/?query=something#fragment") ++ ++ # Remove ASCII tabs and newlines from input as bytes, for http common case scenario. ++ url = b"h\nttp://www.python\n.org\t/java\nscript:\talert('msg\r\n')/?query\n=\tsomething#frag\nment" ++ p = urllib.parse.urlsplit(url) ++ self.assertEqual(p.scheme, b"http") ++ self.assertEqual(p.netloc, b"www.python.org") ++ self.assertEqual(p.path, b"/javascript:alert('msg')/") ++ self.assertEqual(p.query, b"query=something") ++ self.assertEqual(p.fragment, b"fragment") ++ self.assertEqual(p.username, None) ++ self.assertEqual(p.password, None) ++ self.assertEqual(p.hostname, b"www.python.org") ++ self.assertEqual(p.port, None) ++ self.assertEqual(p.geturl(), b"http://www.python.org/javascript:alert('msg')/?query=something#fragment") ++ ++ # any scheme ++ url = "x-new-scheme\t://www.python\n.org\t/java\nscript:\talert('msg\r\n')/?query\n=\tsomething#frag\nment" ++ p = urllib.parse.urlsplit(url) ++ self.assertEqual(p.geturl(), "x-new-scheme://www.python.org/javascript:alert('msg')/?query=something#fragment") ++ ++ # Remove ASCII tabs and newlines from input as bytes, any scheme. ++ url = b"x-new-scheme\t://www.python\n.org\t/java\nscript:\talert('msg\r\n')/?query\n=\tsomething#frag\nment" ++ p = urllib.parse.urlsplit(url) ++ self.assertEqual(p.geturl(), b"x-new-scheme://www.python.org/javascript:alert('msg')/?query=something#fragment") ++ ++ # Unsafe bytes is not returned from urlparse cache. ++ # scheme is stored after parsing, sending an scheme with unsafe bytes *will not* return an unsafe scheme ++ url = "https://www.python\n.org\t/java\nscript:\talert('msg\r\n')/?query\n=\tsomething#frag\nment" ++ scheme = "htt\nps" ++ for _ in range(2): ++ p = urllib.parse.urlsplit(url, scheme=scheme) ++ self.assertEqual(p.scheme, "https") ++ self.assertEqual(p.geturl(), "https://www.python.org/javascript:alert('msg')/?query=something#fragment") ++ + def test_attributes_bad_port(self): + """Check handling of invalid ports.""" + for bytes in (False, True): +diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py +index e67d69db3614b..4f21ce784eee9 100644 +--- a/Lib/urllib/parse.py ++++ b/Lib/urllib/parse.py +@@ -76,6 +76,9 @@ + '0123456789' + '+-.') + ++# Unsafe bytes to be removed per WHATWG spec ++_UNSAFE_URL_BYTES_TO_REMOVE = ['\t', '\r', '\n'] ++ + # XXX: Consider replacing with functools.lru_cache + MAX_CACHE_SIZE = 20 + _parse_cache = {} +@@ -409,6 +412,11 @@ def _checknetloc(netloc): + raise ValueError("netloc '" + netloc + "' contains invalid " + + "characters under NFKC normalization") + ++def _remove_unsafe_bytes_from_url(url): ++ for b in _UNSAFE_URL_BYTES_TO_REMOVE: ++ url = url.replace(b, "") ++ return url ++ + def urlsplit(url, scheme='', allow_fragments=True): + """Parse a URL into 5 components: + :///?# +@@ -416,6 +424,8 @@ def urlsplit(url, scheme='', allow_fragments=True): + Note that we don't break the components up in smaller bits + (e.g. netloc is a single string) and we don't expand % escapes.""" + url, scheme, _coerce_result = _coerce_args(url, scheme) ++ url = _remove_unsafe_bytes_from_url(url) ++ scheme = _remove_unsafe_bytes_from_url(scheme) + allow_fragments = bool(allow_fragments) + key = url, scheme, allow_fragments, type(url), type(scheme) + cached = _parse_cache.get(key, None) +diff --git a/Misc/NEWS.d/next/Security/2021-04-25-07-46-37.bpo-43882.Jpwx85.rst b/Misc/NEWS.d/next/Security/2021-04-25-07-46-37.bpo-43882.Jpwx85.rst +new file mode 100644 +index 0000000000000..a326d079dff4a +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2021-04-25-07-46-37.bpo-43882.Jpwx85.rst +@@ -0,0 +1,6 @@ ++The presence of newline or tab characters in parts of a URL could allow ++some forms of attacks. ++ ++Following the controlling specification for URLs defined by WHATWG ++:func:`urllib.parse` now removes ASCII newlines and tabs from URLs, ++preventing such attacks. diff --git a/python3.spec b/python3.spec index 70e6fc9..136ac92 100644 --- a/python3.spec +++ b/python3.spec @@ -3,7 +3,7 @@ Summary: Interpreter of the Python3 programming language URL: https://www.python.org/ Version: 3.7.9 -Release: 17 +Release: 18 License: Python %global branchversion 3.7 @@ -147,6 +147,8 @@ Patch6036: backport-37193-Remove-thread-objects-which-finished-proce.patch Patch6037: backport-CVE-2021-3733.patch Patch6038: backport-CVE-2021-3737.patch Patch6039: backport-bpo-44022-Improve-the-regression-test.patch +Patch6040: backport-CVE-2021-4189.patch +Patch6041: backport-CVE-2022-0391.patch Recommends: %{name}-help = %{version}-%{release} Provides: python%{branchversion} = %{version}-%{release} @@ -278,6 +280,8 @@ rm Lib/ensurepip/_bundled/*.whl %patch6037 -p1 %patch6038 -p1 %patch6039 -p1 +%patch6040 -p1 +%patch6041 -p1 sed -i "s/generic_os/%{_vendor}/g" Lib/platform.py rm configure pyconfig.h.in @@ -879,6 +883,12 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %{_mandir}/*/* %changelog +* Thu Feb 17 2022 shixuantong - 3.7.9-18 +- Type:CVE +- CVE:CVE-2021-4189 CVE-2022-0391 +- SUG:NA +- DESC:fix CVE-2021-4189 CVE-2022-0391 + * Thu Feb 10 2022 shixuantong - 3.7.9-17 - Type:bugfix - CVE:NA -- Gitee