Skip to content

Commit 8bf8cb5

Browse files
authored
Security fixes submitted to ipy2 (#2016)
1 parent d1e03b7 commit 8bf8cb5

File tree

3 files changed

+50
-19
lines changed

3 files changed

+50
-19
lines changed

src/core/IronPython.StdLib/lib/encodings/idna.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,22 @@ def nameprep(label):
3939

4040
# Check bidi
4141
RandAL = [stringprep.in_table_d1(x) for x in label]
42-
for c in RandAL:
43-
if c:
44-
# There is a RandAL char in the string. Must perform further
45-
# tests:
46-
# 1) The characters in section 5.8 MUST be prohibited.
47-
# This is table C.8, which was already checked
48-
# 2) If a string contains any RandALCat character, the string
49-
# MUST NOT contain any LCat character.
50-
if any(stringprep.in_table_d2(x) for x in label):
51-
raise UnicodeError("Violation of BIDI requirement 2")
52-
53-
# 3) If a string contains any RandALCat character, a
54-
# RandALCat character MUST be the first character of the
55-
# string, and a RandALCat character MUST be the last
56-
# character of the string.
57-
if not RandAL[0] or not RandAL[-1]:
58-
raise UnicodeError("Violation of BIDI requirement 3")
42+
if any(RandAL):
43+
# There is a RandAL char in the string. Must perform further
44+
# tests:
45+
# 1) The characters in section 5.8 MUST be prohibited.
46+
# This is table C.8, which was already checked
47+
# 2) If a string contains any RandALCat character, the string
48+
# MUST NOT contain any LCat character.
49+
if any(stringprep.in_table_d2(x) for x in label):
50+
raise UnicodeError("Violation of BIDI requirement 2")
51+
52+
# 3) If a string contains any RandALCat character, a
53+
# RandALCat character MUST be the first character of the
54+
# string, and a RandALCat character MUST be the last
55+
# character of the string.
56+
if not RandAL[0] or not RandAL[-1]:
57+
raise UnicodeError("Violation of BIDI requirement 3")
5958

6059
return label
6160

src/core/IronPython.StdLib/lib/ftplib.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ class FTP:
105105
welcome = None
106106
passiveserver = 1
107107
encoding = "latin-1"
108+
# Disables https://bugs.python.org/issue43285 security if set to True.
109+
trust_server_pasv_ipv4_address = False
108110

109111
# Initialization method (called by class instantiation).
110112
# Initialize host to localhost, port to standard ftp port
@@ -334,8 +336,13 @@ def makeport(self):
334336
return sock
335337

336338
def makepasv(self):
339+
"""Internal: Does the PASV or EPSV handshake -> (address, port)"""
337340
if self.af == socket.AF_INET:
338-
host, port = parse227(self.sendcmd('PASV'))
341+
untrusted_host, port = parse227(self.sendcmd('PASV'))
342+
if self.trust_server_pasv_ipv4_address:
343+
host = untrusted_host
344+
else:
345+
host = self.sock.getpeername()[0]
339346
else:
340347
host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername())
341348
return host, port

src/core/IronPython.StdLib/lib/test/test_ftplib.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ def __init__(self, conn):
9494
self.rest = None
9595
self.next_retr_data = RETR_DATA
9696
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'
97101

98102
def collect_incoming_data(self, data):
99103
self.in_buffer.append(data)
@@ -136,7 +140,8 @@ def cmd_pasv(self, arg):
136140
sock.bind((self.socket.getsockname()[0], 0))
137141
sock.listen(5)
138142
sock.settimeout(TIMEOUT)
139-
ip, port = sock.getsockname()[:2]
143+
port = sock.getsockname()[1]
144+
ip = self.fake_pasv_server_ip
140145
ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256
141146
self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2))
142147
conn, addr = sock.accept()
@@ -689,6 +694,26 @@ def test_makepasv(self):
689694
# IPv4 is in use, just make sure send_epsv has not been used
690695
self.assertEqual(self.server.handler_instance.last_received_cmd, 'pasv')
691696

697+
def test_makepasv_issue43285_security_disabled(self):
698+
"""Test the opt-in to the old vulnerable behavior."""
699+
self.client.trust_server_pasv_ipv4_address = True
700+
bad_host, port = self.client.makepasv()
701+
self.assertEqual(
702+
bad_host, self.server.handler_instance.fake_pasv_server_ip)
703+
# Opening and closing a connection keeps the dummy server happy
704+
# instead of timing out on accept.
705+
socket.create_connection((self.client.sock.getpeername()[0], port),
706+
timeout=TIMEOUT).close()
707+
708+
def test_makepasv_issue43285_security_enabled_default(self):
709+
self.assertFalse(self.client.trust_server_pasv_ipv4_address)
710+
trusted_host, port = self.client.makepasv()
711+
self.assertNotEqual(
712+
trusted_host, self.server.handler_instance.fake_pasv_server_ip)
713+
# Opening and closing a connection keeps the dummy server happy
714+
# instead of timing out on accept.
715+
socket.create_connection((trusted_host, port), timeout=TIMEOUT).close()
716+
692717
def test_with_statement(self):
693718
self.client.quit()
694719

0 commit comments

Comments
 (0)