Title: [security] smtplib multiple CRLF injection
Type: security Stage: patch review
Components: email Versions: Python 3.10, Python 3.9, Python 3.8, Python 3.7, Python 3.6
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: barry, christian.heimes, lukasz.langa, martin.ortner, miguendes, ned.deily, r.david.murray
Priority: deferred blocker Keywords: patch

Created on 2021-02-04 13:05 by martin.ortner, last changed 2021-05-21 21:32 by ned.deily.

Pull Requests
URL Status Linked Edit
PR 25987 open miguendes, 2021-05-08 10:53
Messages (6)
msg386481 - (view) Author: Martin Ortner (martin.ortner) Date: 2021-02-04 13:05
// reported via PSRT email (see timeline; last contact: Alex/PSRT)
// external reference:

vendor: python
authors: tintinweb
affectedVersions: [at least 3.8.3, <=3.7.8, <=3.6.11, <=3.5.9, <=2.7.18]
vulnClass: CWE-93

# Vulnerability Note

## 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.

## Details

### Description

The root cause of this (and probably also some earlier reported CR-LF injections) is the method `putcmd()` in `lib/`[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)
        str = '%s %s%s' % (cmd, args, CRLF)

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

## Proof of Concept

1. set-up a local tcp listener `⇒  nc -l 10001`

2. 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.sendmail("", "", "wazzuuup\nlinetwo")
server.mail("",["X-OPTION\nX-INJECTED-1","X-OPTION2\nX-INJECTED-2"]) # options CRLF injection


3. interact with `smtplib`, check for `X-INJECTED`

⇒  nc -l 10001                       
nc -l 10001
220 yo
ehlo hi
mail FROM:<>
250 ok
rcpt TO:<>
250 ok
354 End data with <CR><LF>.<CR><LF> 
250 ok
250 ok
250 ok

### Proposed Fix

* enforce that `putcmd` emits exactly one command at a time and encode `\n -> \\n`.

diff --git a/Lib/ b/Lib/
index e2dbbbc..9c16e7d 100755
--- a/Lib/
+++ b/Lib/
@@ -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
-            str = '%s %s%s' % (cmd, args, CRLF)
-        self.send(str)
+            str = '%s %s' % (cmd, args)
+        self.send('%s%s' % (str.replace('\n','\\n'), CRLF))

## Vendor Response

Vendor response: gone silent

### 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.

## References

* [1]
* [2]
* [3]
* [4]
* [5]
msg390048 - (view) Author: Łukasz Langa (lukasz.langa) * (Python committer) Date: 2021-04-02 09:35
Deferred the blocker to a regular release due to lack of activity in time for the current expedited releases.
msg393206 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2021-05-07 19:14
Still in "deferred blocker" status awaiting a PR from someone
msg393208 - (view) Author: Miguel Brito (miguendes) * Date: 2021-05-07 19:27
If there's no one working on it I'd be happy to prepare a fix.
msg393209 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2021-05-07 19:32
There is no sign of anyone currently working on it, so please feel free to dig in!
msg394160 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2021-05-21 21:32
Thanks for the PR!  Can someone from the email team take a look at it, please?
Date User Action Args
2021-05-21 21:32:20ned.deilysetmessages: + msg394160
2021-05-08 10:53:28miguendessetkeywords: + patch
stage: patch review
pull_requests: + pull_request24639
2021-05-07 19:32:34ned.deilysetmessages: + msg393209
2021-05-07 19:27:42miguendessetnosy: + miguendes
messages: + msg393208
2021-05-07 19:14:19ned.deilysetmessages: + msg393206
2021-04-02 09:35:30lukasz.langasetpriority: release blocker -> deferred blocker

messages: + msg390048
2021-03-31 19:58:35christian.heimessetpriority: normal -> release blocker
nosy: + christian.heimes, ned.deily, lukasz.langa
2021-02-04 13:58:08vstinnersettitle: smtplib multiple CRLF injection -> [security] smtplib multiple CRLF injection
2021-02-04 13:05:34martin.ortnercreate