[Python-checkins] [3.6] bpo-43124: Fix smtplib multiple CRLF injection (GH-25987) (GH-28038)
ned-deily
webhook-mailer at python.org
Mon Aug 30 15:22:05 EDT 2021
https://github.com/python/cpython/commit/29d97d17fb7adab3b0df9e178b73f70292d1cf64
commit: 29d97d17fb7adab3b0df9e178b73f70292d1cf64
branch: 3.6
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: ned-deily <nad at python.org>
date: 2021-08-30T15:21:57-04:00
summary:
[3.6] bpo-43124: Fix smtplib multiple CRLF injection (GH-25987) (GH-28038)
Co-authored-by: Miguel Brito <5544985+miguendes at users.noreply.github.com>
Co-authored-by: Łukasz Langa <lukasz at langa.pl>
(cherry picked from commit 0897253f426068ea6a6fbe0ada01689af9ef1019)
files:
A Misc/NEWS.d/next/Security/2021-05-08-11-50-46.bpo-43124.2CTM6M.rst
M Lib/smtplib.py
M Lib/test/test_smtplib.py
diff --git a/Lib/smtplib.py b/Lib/smtplib.py
index 6091c7fb7acad..e3c5b118d9a6e 100755
--- a/Lib/smtplib.py
+++ b/Lib/smtplib.py
@@ -361,10 +361,15 @@ def send(self, s):
def putcmd(self, cmd, args=""):
"""Send a command to the server."""
if args == "":
- str = '%s%s' % (cmd, CRLF)
+ s = cmd
else:
- str = '%s %s%s' % (cmd, args, CRLF)
- self.send(str)
+ s = f'{cmd} {args}'
+ 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):
"""Get a reply from the server.
diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py
index 87047514e7aad..121bbcc9dbd91 100644
--- a/Lib/test/test_smtplib.py
+++ b/Lib/test/test_smtplib.py
@@ -286,6 +286,16 @@ def testEXPNNotImplemented(self):
self.assertEqual(smtp.getreply(), expected)
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=10) # support.LOOPBACK_TIMEOUT in newer Pythons
+ 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):
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
expected = (252, b'Cannot VRFY user, but will accept message ' + \
@@ -355,6 +365,51 @@ def testSendNeedingDotQuote(self):
mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END)
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=10) # support.LOOPBACK_TIMEOUT in newer Pythons
+ self.addCleanup(smtp.close)
+ with self.assertRaises(ValueError) as exc:
+ smtp.sendmail("hi at me.com", "you at 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=10) # support.LOOPBACK_TIMEOUT in newer Pythons
+
+ self.addCleanup(smtp.close)
+ smtp.sendmail("hi at me.com", "you at me.com", m)
+ with self.assertRaises(ValueError) as exc:
+ smtp.mail("hi at 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):
m = 'A test message'
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
diff --git a/Misc/NEWS.d/next/Security/2021-05-08-11-50-46.bpo-43124.2CTM6M.rst b/Misc/NEWS.d/next/Security/2021-05-08-11-50-46.bpo-43124.2CTM6M.rst
new file mode 100644
index 0000000000000..e897d6cd3641d
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2021-05-08-11-50-46.bpo-43124.2CTM6M.rst
@@ -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.
More information about the Python-checkins
mailing list