Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0897253

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

File tree

Expand file treeCollapse file tree

3 files changed

+65
-3
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+65
-3
lines changed

‎Lib/smtplib.py

Copy file name to clipboardExpand all lines: Lib/smtplib.py
+8-3Lines 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

Copy file name to clipboardExpand all lines: Lib/test/test_smtplib.py
+55Lines changed: 55 additions & 0 deletions
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',
+2Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.