Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32k
Description
Bug report
Bug description:
In certain cases, withEmailMessage
objects, encoded headers can fold with double line endings, causing breakage of flattened message objects, making messages display improperly in email clients and rendering attachments inaccessible.
Python3.11.11 does not exhibit this behaviour. But numerous other Python versions eg3.11.9
,3.12.x
,3.10.x
and3.9.x
are affected by the bug.
The specific malformation is where:
- a header is long
- a header is presented with its value presented as RFC 2047 UTF8 base64
- when decoded, the header's payload ends with a newline
\n
An example of this:
Subject: =?utf-8?B?Vm9pY2Vib3ggRmlybWE6IFRlc3QtTmFjaHJpY2h0IHVtIDEwOjAzOjM1IDA0LjA0LjI1IC0gQUdGRU8gRVMgNTIyIElUIHVwIC0gc3RhaXBzbmV0Cg==?=
Here is a test script which reproduces the problem on many/most recent Python versions:
#!/usr/bin/env python3"""Reproduces a bug with flattening email headers whose encoded payloads end in a newline"""importsysfromemailimportpolicyfromemail.parserimportParser,BytesParser,HeaderParsersampleRaw="""Date: Fri, 04 Apr 2025 10:03:35 +0200\rFrom: sender@foo.com\rSubject: =?utf-8?B?Vm9pY2Vib3ggRmlybWE6IFRlc3QtTmFjaHJpY2h0IHVtIDEwOjAzOjM1IDA0LjA0LjI1IC0gQUdGRU8gRVMgNTIyIElUIHVwIC0gc3RhaXBzbmV0Cg==?=\rMIME-Version: 1.0\rContent-Type: multipart/mixed;\r boundary="235711131719"\rTo: recipient@bar.com\r\rThis is a multi-part message in MIME format.\r--235711131719\rContent-Type: text/plain; charset=UTF-8; format=flowed\rContent-Transfer-Encoding: 7bit\r\rThis is readable part of the body\r--235711131719--\r\r"""messageObj=Parser(policy=policy.default).parsestr(sampleRaw)print(sys.version)print("--------")print(messageObj.as_string())print("--------")
Python 3.11.11 flattens the message correctly. Here is an excerpt in/around theSubject:
header:
From: sender@foo.comSubject: Voicebox Firma: Test-Nachricht um 10:03:35 04.04.25 - AGFEO ES 522 IT up - =?utf-8?q?staipsnet=0A?=MIME-Version: 1.0Content-Type: multipart/mixed; boundary="235711131719"To: recipient@bar.comThis is a multi-part message in MIME format.
As can be seen here, the header has been re-wrapped to quoted-printable (which is fine), the embedded newline is present in the encoded payload, but when the header is folded out, it has only the one line ending. Great.
But other pythons I've tested with come up with:
From: sender@foo.comSubject: Voicebox Firma: Test-Nachricht um 10:03:35 04.04.25 - AGFEO ES 522 IT up - staipsnetMIME-Version: 1.0Content-Type: multipart/mixed; boundary="235711131719"To: recipient@bar.comThis is a multi-part message in MIME format.--235711131719Content-Type: text/plain; charset=UTF-8; format=flowedContent-Transfer-Encoding: 7bitThis is readable part of the body
Note the blank line afterSubject:
.
When messages folded and delivered with this breakage are delivered, MTAs will often addContent-Type: text/plain
because the headers after Subject have been lost into the body.
The recipient of the above message will see this formatted as plain text in their viewing pane:
MIME-Version: 1.0Content-Type: multipart/mixed; boundary="235711131719"To: recipient@bar.comThis is a multi-part message in MIME format.--235711131719Content-Type: text/plain; charset=UTF-8; format=flowedContent-Transfer-Encoding: 7bitThis is readable part of the body
If there were any attachments, their encoded representations will appear after that as gibberish strings, and the mail client won't indicate an attachment is present.
This has potentially serious security implications. If an email delivery chain has python stdlib-based mail processing at or near the end, a carefully structured malicious header can
- evade sanitisation
- nullify and/or replace headers added upstream in the chain
- inject malicious extra headers and MIME-encoded context which may cause subsequent handling steps, and/or the final MUA, to be hijacked for exploit attempts.
Workaround for me has been to subclass a policy, and implement a_fold()
method to get the parent class' folding, then.rstrip()
it, then add"\r\n"
to the end.
CPython versions tested on:
3.12
Operating systems tested on:
Linux