| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a99860e commit 7937395
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2585,3 +2585,12 @@ separator key, with ``&`` as the default. This change also affects | |||
| 2585 | 2585 | functions internally. For more details, please see their respective | |
| 2586 | 2586 | documentation. | |
| 2587 | 2587 | (Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.) | |
| 2588 | + | ||
| 2589 | + Notable changes in Python 3.7.11 | ||
| 2590 | + ================================ | ||
| 2591 | + | ||
| 2592 | + A security fix alters the :class:`ftplib.FTP` behavior to not trust the | ||
| 2593 | + IPv4 address sent from the remote server when setting up a passive data | ||
| 2594 | + channel. We reuse the ftp server IP address instead. For unusual code | ||
| 2595 | + requiring the old behavior, set a ``trust_server_pasv_ipv4_address`` | ||
| 2596 | + attribute on your FTP instance to ``True``. (See :issue:`43285`) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -104,6 +104,8 @@ class FTP: | |||
| 104 | 104 | welcome = None | |
| 105 | 105 | passiveserver = 1 | |
| 106 | 106 | encoding = "latin-1" | |
| 107 | + # Disables https://bugs.python.org/issue43285 security if set to True. | ||
| 108 | + trust_server_pasv_ipv4_address = False | ||
| 107 | 109 | ||
| 108 | 110 | # Initialization method (called by class instantiation). | |
| 109 | 111 | # Initialize host to localhost, port to standard ftp port | |
@@ -333,8 +335,13 @@ def makeport(self): | |||
| 333 | 335 | return sock | |
| 334 | 336 | ||
| 335 | 337 | def makepasv(self): | |
| 338 | + """Internal: Does the PASV or EPSV handshake -> (address, port)""" | ||
| 336 | 339 | if self.af == socket.AF_INET: | |
| 337 | - host, port = parse227(self.sendcmd('PASV')) | ||
| 340 | + untrusted_host, port = parse227(self.sendcmd('PASV')) | ||
| 341 | + if self.trust_server_pasv_ipv4_address: | ||
| 342 | + host = untrusted_host | ||
| 343 | + else: | ||
| 344 | + host = self.sock.getpeername()[0] | ||
| 338 | 345 | else: | |
| 339 | 346 | host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername()) | |
| 340 | 347 | return host, port | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -94,6 +94,10 @@ def __init__(self, conn): | |||
| 94 | 94 | self.rest = None | |
| 95 | 95 | self.next_retr_data = RETR_DATA | |
| 96 | 96 | self.push('220 welcome') | |
| 97 | + # We use this as the string IPv4 address to direct the client | ||
| 98 | + # to in response to a PASV command. To test security behavior. | ||
| 99 | + # https://bugs.python.org/issue43285/. | ||
| 100 | + self.fake_pasv_server_ip = '252.253.254.255' | ||
| 97 | 101 | ||
| 98 | 102 | def collect_incoming_data(self, data): | |
| 99 | 103 | self.in_buffer.append(data) | |
@@ -136,7 +140,8 @@ def cmd_pasv(self, arg): | |||
| 136 | 140 | sock.bind((self.socket.getsockname()[0], 0)) | |
| 137 | 141 | sock.listen() | |
| 138 | 142 | sock.settimeout(TIMEOUT) | |
| 139 | - ip, port = sock.getsockname()[:2] | ||
| 143 | + port = sock.getsockname()[1] | ||
| 144 | + ip = self.fake_pasv_server_ip | ||
| 140 | 145 | ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256 | |
| 141 | 146 | self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2)) | |
| 142 | 147 | conn, addr = sock.accept() | |
@@ -698,6 +703,26 @@ def test_makepasv(self): | |||
| 698 | 703 | # IPv4 is in use, just make sure send_epsv has not been used | |
| 699 | 704 | self.assertEqual(self.server.handler_instance.last_received_cmd, 'pasv') | |
| 700 | 705 | ||
| 706 | + def test_makepasv_issue43285_security_disabled(self): | ||
| 707 | + """Test the opt-in to the old vulnerable behavior.""" | ||
| 708 | + self.client.trust_server_pasv_ipv4_address = True | ||
| 709 | + bad_host, port = self.client.makepasv() | ||
| 710 | + self.assertEqual( | ||
| 711 | + bad_host, self.server.handler_instance.fake_pasv_server_ip) | ||
| 712 | + # Opening and closing a connection keeps the dummy server happy | ||
| 713 | + # instead of timing out on accept. | ||
| 714 | + socket.create_connection((self.client.sock.getpeername()[0], port), | ||
| 715 | + timeout=TIMEOUT).close() | ||
| 716 | + | ||
| 717 | + def test_makepasv_issue43285_security_enabled_default(self): | ||
| 718 | + self.assertFalse(self.client.trust_server_pasv_ipv4_address) | ||
| 719 | + trusted_host, port = self.client.makepasv() | ||
| 720 | + self.assertNotEqual( | ||
| 721 | + trusted_host, self.server.handler_instance.fake_pasv_server_ip) | ||
| 722 | + # Opening and closing a connection keeps the dummy server happy | ||
| 723 | + # instead of timing out on accept. | ||
| 724 | + socket.create_connection((trusted_host, port), timeout=TIMEOUT).close() | ||
| 725 | + | ||
| 701 | 726 | def test_with_statement(self): | |
| 702 | 727 | self.client.quit() | |
| 703 | 728 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,8 @@ | |||
| 1 | + :mod:`ftplib` no longer trusts the IP address value returned from the server | ||
| 2 | + in response to the PASV command by default. This prevents a malicious FTP | ||
| 3 | + server from using the response to probe IPv4 address and port combinations | ||
| 4 | + on the client network. | ||
| 5 | + | ||
| 6 | + Code that requires the former vulnerable behavior may set a | ||
| 7 | + ``trust_server_pasv_ipv4_address`` attribute on their | ||
| 8 | + :class:`ftplib.FTP` instances to ``True`` to re-enable it. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments