Skip to content

Commit 29d97d1

Browse files
[3.6] bpo-43124: Fix smtplib multiple CRLF injection (pythonGH-25987) (pythonGH-28038)
Co-authored-by: Miguel Brito <[email protected]> Co-authored-by: Łukasz Langa <[email protected]> (cherry picked from commit 0897253)
1 parent da9d6c5 commit 29d97d1

File tree

3 files changed

+65
-3
lines changed

3 files changed

+65
-3
lines changed

Lib/smtplib.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -361,10 +361,15 @@ def send(self, s):
361361
def putcmd(self, cmd, args=""):
362362
"""Send a command to the server."""
363363
if args == "":
364-
str = '%s%s' % (cmd, CRLF)
364+
s = cmd
365365
else:
366-
str = '%s %s%s' % (cmd, args, CRLF)
367-
self.send(str)
366+
s = f'{cmd} {args}'
367+
if '\r' in s or '\n' in s:
368+
s = s.replace('\n', '\\n').replace('\r', '\\r')
369+
raise ValueError(
370+
f'command and arguments contain prohibited newline characters: {s}'
371+
)
372+
self.send(f'{s}{CRLF}')
368373

369374
def getreply(self):
370375
"""Get a reply from the server.

Lib/test/test_smtplib.py

+55
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,16 @@ def testEXPNNotImplemented(self):
286286
self.assertEqual(smtp.getreply(), expected)
287287
smtp.quit()
288288

289+
def test_issue43124_putcmd_escapes_newline(self):
290+
# see: https://bugs.python.org/issue43124
291+
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
292+
timeout=10) # support.LOOPBACK_TIMEOUT in newer Pythons
293+
self.addCleanup(smtp.close)
294+
with self.assertRaises(ValueError) as exc:
295+
smtp.putcmd('helo\nX-INJECTED')
296+
self.assertIn("prohibited newline characters", str(exc.exception))
297+
smtp.quit()
298+
289299
def testVRFY(self):
290300
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
291301
expected = (252, b'Cannot VRFY user, but will accept message ' + \
@@ -355,6 +365,51 @@ def testSendNeedingDotQuote(self):
355365
mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END)
356366
self.assertEqual(self.output.getvalue(), mexpect)
357367

368+
def test_issue43124_escape_localhostname(self):
369+
# see: https://bugs.python.org/issue43124
370+
# connect and send mail
371+
m = 'wazzuuup\nlinetwo'
372+
smtp = smtplib.SMTP(HOST, self.port, local_hostname='hi\nX-INJECTED',
373+
timeout=10) # support.LOOPBACK_TIMEOUT in newer Pythons
374+
self.addCleanup(smtp.close)
375+
with self.assertRaises(ValueError) as exc:
376+
smtp.sendmail("[email protected]", "[email protected]", m)
377+
self.assertIn(
378+
"prohibited newline characters: ehlo hi\\nX-INJECTED",
379+
str(exc.exception),
380+
)
381+
# XXX (see comment in testSend)
382+
time.sleep(0.01)
383+
smtp.quit()
384+
385+
debugout = smtpd.DEBUGSTREAM.getvalue()
386+
self.assertNotIn("X-INJECTED", debugout)
387+
388+
def test_issue43124_escape_options(self):
389+
# see: https://bugs.python.org/issue43124
390+
# connect and send mail
391+
m = 'wazzuuup\nlinetwo'
392+
smtp = smtplib.SMTP(
393+
HOST, self.port, local_hostname='localhost',
394+
timeout=10) # support.LOOPBACK_TIMEOUT in newer Pythons
395+
396+
self.addCleanup(smtp.close)
397+
smtp.sendmail("[email protected]", "[email protected]", m)
398+
with self.assertRaises(ValueError) as exc:
399+
smtp.mail("[email protected]", ["X-OPTION\nX-INJECTED-1", "X-OPTION2\nX-INJECTED-2"])
400+
msg = str(exc.exception)
401+
self.assertIn("prohibited newline characters", msg)
402+
self.assertIn("X-OPTION\\nX-INJECTED-1 X-OPTION2\\nX-INJECTED-2", msg)
403+
# XXX (see comment in testSend)
404+
time.sleep(0.01)
405+
smtp.quit()
406+
407+
debugout = smtpd.DEBUGSTREAM.getvalue()
408+
self.assertNotIn("X-OPTION", debugout)
409+
self.assertNotIn("X-OPTION2", debugout)
410+
self.assertNotIn("X-INJECTED-1", debugout)
411+
self.assertNotIn("X-INJECTED-2", debugout)
412+
358413
def testSendNullSender(self):
359414
m = 'A test message'
360415
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Made the internal ``putcmd`` function in :mod:`smtplib` sanitize input for
2+
presence of ``\r`` and ``\n`` characters to avoid (unlikely) command injection.

0 commit comments

Comments
 (0)