Skip to content

Commit 24416e4

Browse files
bpo-43124: Fix smtplib multiple CRLF injection (GH-25987) (GH-28035)
Co-authored-by: Łukasz Langa <[email protected]> (cherry picked from commit 0897253) Co-authored-by: Miguel Brito <[email protected]>
1 parent 007221a commit 24416e4

File tree

3 files changed

+65
-3
lines changed

3 files changed

+65
-3
lines changed

Lib/smtplib.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,10 +367,15 @@ def send(self, s):
367367
def putcmd(self, cmd, args=""):
368368
"""Send a command to the server."""
369369
if args == "":
370-
str = '%s%s' % (cmd, CRLF)
370+
s = cmd
371371
else:
372-
str = '%s %s%s' % (cmd, args, CRLF)
373-
self.send(str)
372+
s = f'{cmd} {args}'
373+
if '\r' in s or '\n' in s:
374+
s = s.replace('\n', '\\n').replace('\r', '\\r')
375+
raise ValueError(
376+
f'command and arguments contain prohibited newline characters: {s}'
377+
)
378+
self.send(f'{s}{CRLF}')
374379

375380
def getreply(self):
376381
"""Get a reply from the server.

Lib/test/test_smtplib.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,16 @@ def testEXPNNotImplemented(self):
332332
self.assertEqual(smtp.getreply(), expected)
333333
smtp.quit()
334334

335+
def test_issue43124_putcmd_escapes_newline(self):
336+
# see: https://bugs.python.org/issue43124
337+
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
338+
timeout=support.LOOPBACK_TIMEOUT)
339+
self.addCleanup(smtp.close)
340+
with self.assertRaises(ValueError) as exc:
341+
smtp.putcmd('helo\nX-INJECTED')
342+
self.assertIn("prohibited newline characters", str(exc.exception))
343+
smtp.quit()
344+
335345
def testVRFY(self):
336346
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
337347
timeout=support.LOOPBACK_TIMEOUT)
@@ -413,6 +423,51 @@ def testSendNeedingDotQuote(self):
413423
mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END)
414424
self.assertEqual(self.output.getvalue(), mexpect)
415425

426+
def test_issue43124_escape_localhostname(self):
427+
# see: https://bugs.python.org/issue43124
428+
# connect and send mail
429+
m = 'wazzuuup\nlinetwo'
430+
smtp = smtplib.SMTP(HOST, self.port, local_hostname='hi\nX-INJECTED',
431+
timeout=support.LOOPBACK_TIMEOUT)
432+
self.addCleanup(smtp.close)
433+
with self.assertRaises(ValueError) as exc:
434+
smtp.sendmail("[email protected]", "[email protected]", m)
435+
self.assertIn(
436+
"prohibited newline characters: ehlo hi\\nX-INJECTED",
437+
str(exc.exception),
438+
)
439+
# XXX (see comment in testSend)
440+
time.sleep(0.01)
441+
smtp.quit()
442+
443+
debugout = smtpd.DEBUGSTREAM.getvalue()
444+
self.assertNotIn("X-INJECTED", debugout)
445+
446+
def test_issue43124_escape_options(self):
447+
# see: https://bugs.python.org/issue43124
448+
# connect and send mail
449+
m = 'wazzuuup\nlinetwo'
450+
smtp = smtplib.SMTP(
451+
HOST, self.port, local_hostname='localhost',
452+
timeout=support.LOOPBACK_TIMEOUT)
453+
454+
self.addCleanup(smtp.close)
455+
smtp.sendmail("[email protected]", "[email protected]", m)
456+
with self.assertRaises(ValueError) as exc:
457+
smtp.mail("[email protected]", ["X-OPTION\nX-INJECTED-1", "X-OPTION2\nX-INJECTED-2"])
458+
msg = str(exc.exception)
459+
self.assertIn("prohibited newline characters", msg)
460+
self.assertIn("X-OPTION\\nX-INJECTED-1 X-OPTION2\\nX-INJECTED-2", msg)
461+
# XXX (see comment in testSend)
462+
time.sleep(0.01)
463+
smtp.quit()
464+
465+
debugout = smtpd.DEBUGSTREAM.getvalue()
466+
self.assertNotIn("X-OPTION", debugout)
467+
self.assertNotIn("X-OPTION2", debugout)
468+
self.assertNotIn("X-INJECTED-1", debugout)
469+
self.assertNotIn("X-INJECTED-2", debugout)
470+
416471
def testSendNullSender(self):
417472
m = 'A test message'
418473
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
Lines changed: 2 additions & 0 deletions
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)