diff options
author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2021-08-30 12:16:24 -0700 |
---|---|---|
committer | Michał Górny <mgorny@gentoo.org> | 2021-08-30 23:53:54 +0200 |
commit | f5147181cc58806f22226d4079c0c7ff76f0051f (patch) | |
tree | 79c9a4146b2e7ffc34b47a2f911a1baa1f956e61 | |
parent | bpo-43998: Default to TLS 1.2 and increase cipher suite security (GH-25778) (diff) | |
download | cpython-f5147181cc58806f22226d4079c0c7ff76f0051f.tar.gz cpython-f5147181cc58806f22226d4079c0c7ff76f0051f.tar.bz2 cpython-f5147181cc58806f22226d4079c0c7ff76f0051f.zip |
[3.7] bpo-43124: Fix smtplib multiple CRLF injection (GH-25987) (GH-28037)
Co-authored-by: Miguel Brito <5544985+miguendes@users.noreply.github.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
(cherry picked from commit 0897253f426068ea6a6fbe0ada01689af9ef1019)
-rwxr-xr-x | Lib/smtplib.py | 11 | ||||
-rw-r--r-- | Lib/test/test_smtplib.py | 55 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Security/2021-05-08-11-50-46.bpo-43124.2CTM6M.rst | 2 |
3 files changed, 65 insertions, 3 deletions
diff --git a/Lib/smtplib.py b/Lib/smtplib.py index 22d5097c697..345f03d5387 100755 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -361,10 +361,15 @@ class SMTP: 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 b4149d3ef00..a8c7402e34e 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -282,6 +282,16 @@ class DebuggingServerTests(unittest.TestCase): 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 ' + \ @@ -351,6 +361,51 @@ class DebuggingServerTests(unittest.TestCase): 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@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=10) # support.LOOPBACK_TIMEOUT in newer Pythons + + 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): 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 00000000000..e897d6cd364 --- /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. |