mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-43124: Fix smtplib multiple CRLF injection (GH-25987)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
parent
3fc5d84046
commit
0897253f42
3 changed files with 65 additions and 3 deletions
|
@ -367,10 +367,15 @@ class SMTP:
|
||||||
def putcmd(self, cmd, args=""):
|
def putcmd(self, cmd, args=""):
|
||||||
"""Send a command to the server."""
|
"""Send a command to the server."""
|
||||||
if args == "":
|
if args == "":
|
||||||
str = '%s%s' % (cmd, CRLF)
|
s = cmd
|
||||||
else:
|
else:
|
||||||
str = '%s %s%s' % (cmd, args, CRLF)
|
s = f'{cmd} {args}'
|
||||||
self.send(str)
|
if '\r' in s or '\n' in s:
|
||||||
|
s = s.replace('\n', '\\n').replace('\r', '\\r')
|
||||||
|
raise ValueError(
|
||||||
|
f'command and arguments contain prohibited newline characters: {s}'
|
||||||
|
)
|
||||||
|
self.send(f'{s}{CRLF}')
|
||||||
|
|
||||||
def getreply(self):
|
def getreply(self):
|
||||||
"""Get a reply from the server.
|
"""Get a reply from the server.
|
||||||
|
|
|
@ -336,6 +336,16 @@ class DebuggingServerTests(unittest.TestCase):
|
||||||
self.assertEqual(smtp.getreply(), expected)
|
self.assertEqual(smtp.getreply(), expected)
|
||||||
smtp.quit()
|
smtp.quit()
|
||||||
|
|
||||||
|
def test_issue43124_putcmd_escapes_newline(self):
|
||||||
|
# see: https://bugs.python.org/issue43124
|
||||||
|
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
|
||||||
|
timeout=support.LOOPBACK_TIMEOUT)
|
||||||
|
self.addCleanup(smtp.close)
|
||||||
|
with self.assertRaises(ValueError) as exc:
|
||||||
|
smtp.putcmd('helo\nX-INJECTED')
|
||||||
|
self.assertIn("prohibited newline characters", str(exc.exception))
|
||||||
|
smtp.quit()
|
||||||
|
|
||||||
def testVRFY(self):
|
def testVRFY(self):
|
||||||
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
|
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
|
||||||
timeout=support.LOOPBACK_TIMEOUT)
|
timeout=support.LOOPBACK_TIMEOUT)
|
||||||
|
@ -417,6 +427,51 @@ class DebuggingServerTests(unittest.TestCase):
|
||||||
mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END)
|
mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END)
|
||||||
self.assertEqual(self.output.getvalue(), mexpect)
|
self.assertEqual(self.output.getvalue(), mexpect)
|
||||||
|
|
||||||
|
def test_issue43124_escape_localhostname(self):
|
||||||
|
# see: https://bugs.python.org/issue43124
|
||||||
|
# connect and send mail
|
||||||
|
m = 'wazzuuup\nlinetwo'
|
||||||
|
smtp = smtplib.SMTP(HOST, self.port, local_hostname='hi\nX-INJECTED',
|
||||||
|
timeout=support.LOOPBACK_TIMEOUT)
|
||||||
|
self.addCleanup(smtp.close)
|
||||||
|
with self.assertRaises(ValueError) as exc:
|
||||||
|
smtp.sendmail("hi@me.com", "you@me.com", m)
|
||||||
|
self.assertIn(
|
||||||
|
"prohibited newline characters: ehlo hi\\nX-INJECTED",
|
||||||
|
str(exc.exception),
|
||||||
|
)
|
||||||
|
# XXX (see comment in testSend)
|
||||||
|
time.sleep(0.01)
|
||||||
|
smtp.quit()
|
||||||
|
|
||||||
|
debugout = smtpd.DEBUGSTREAM.getvalue()
|
||||||
|
self.assertNotIn("X-INJECTED", debugout)
|
||||||
|
|
||||||
|
def test_issue43124_escape_options(self):
|
||||||
|
# see: https://bugs.python.org/issue43124
|
||||||
|
# connect and send mail
|
||||||
|
m = 'wazzuuup\nlinetwo'
|
||||||
|
smtp = smtplib.SMTP(
|
||||||
|
HOST, self.port, local_hostname='localhost',
|
||||||
|
timeout=support.LOOPBACK_TIMEOUT)
|
||||||
|
|
||||||
|
self.addCleanup(smtp.close)
|
||||||
|
smtp.sendmail("hi@me.com", "you@me.com", m)
|
||||||
|
with self.assertRaises(ValueError) as exc:
|
||||||
|
smtp.mail("hi@me.com", ["X-OPTION\nX-INJECTED-1", "X-OPTION2\nX-INJECTED-2"])
|
||||||
|
msg = str(exc.exception)
|
||||||
|
self.assertIn("prohibited newline characters", msg)
|
||||||
|
self.assertIn("X-OPTION\\nX-INJECTED-1 X-OPTION2\\nX-INJECTED-2", msg)
|
||||||
|
# XXX (see comment in testSend)
|
||||||
|
time.sleep(0.01)
|
||||||
|
smtp.quit()
|
||||||
|
|
||||||
|
debugout = smtpd.DEBUGSTREAM.getvalue()
|
||||||
|
self.assertNotIn("X-OPTION", debugout)
|
||||||
|
self.assertNotIn("X-OPTION2", debugout)
|
||||||
|
self.assertNotIn("X-INJECTED-1", debugout)
|
||||||
|
self.assertNotIn("X-INJECTED-2", debugout)
|
||||||
|
|
||||||
def testSendNullSender(self):
|
def testSendNullSender(self):
|
||||||
m = 'A test message'
|
m = 'A test message'
|
||||||
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
|
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Made the internal ``putcmd`` function in :mod:`smtplib` sanitize input for
|
||||||
|
presence of ``\r`` and ``\n`` characters to avoid (unlikely) command injection.
|
Loading…
Add table
Add a link
Reference in a new issue