Skip to content

Commit 0897253

Browse files
miguendesambv
andauthored
bpo-43124: Fix smtplib multiple CRLF injection (pythonGH-25987)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 3fc5d84 commit 0897253

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
@@ -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

+55
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,16 @@ def testEXPNNotImplemented(self):
336336
self.assertEqual(smtp.getreply(), expected)
337337
smtp.quit()
338338

339+
def test_issue43124_putcmd_escapes_newline(self):
340+
# see: https://bugs.python.org/issue43124
341+
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
342+
timeout=support.LOOPBACK_TIMEOUT)
343+
self.addCleanup(smtp.close)
344+
with self.assertRaises(ValueError) as exc:
345+
smtp.putcmd('helo\nX-INJECTED')
346+
self.assertIn("prohibited newline characters", str(exc.exception))
347+
smtp.quit()
348+
339349
def testVRFY(self):
340350
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
341351
timeout=support.LOOPBACK_TIMEOUT)
@@ -417,6 +427,51 @@ def testSendNeedingDotQuote(self):
417427
mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END)
418428
self.assertEqual(self.output.getvalue(), mexpect)
419429

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