During our advent of PHP application vulnerabilities, we reported a remote command execution vulnerability in the popular webmailer Roundcube (CVE-2016-9920). This vulnerability allowed a malicious user to execute arbitrary system commands on the targeted server by simply writing an email via the Roundcube interface. After we reported the vulnerability to the vendor and released our blog post, similar security vulnerabilities that base on PHP’s built-in mail()
function popped up in other PHP applications 1 2 3 4. In this post, we have a look at the common ground of these vulnerabilities, which security patches are faulty, and how to use mail()
securely.
PHP comes with the built-in function mail()
for sending emails from a PHP application. The mail delivery can be configured by using the following five parameters.
|
|
The first three parameters of this function are self-explanatory and less sensitive, as these are not affected by injection attacks. Still, be aware that if the to parameter can be controlled by the user, she can send spam emails to an arbitrary address.
The last two optional parameters are more concerning. The fourth parameter $additional_headers
receives a string which is appended to the email header. Here, additional email headers can be specified, for example From:
and Reply-To:
. Since mail headers are separated by the CRLF newline character \r\n
5, an attacker can use these characters to append additional email headers when user input is used unsanitized in the fourth parameter. This attack is known as Email Header Injection (or short Email Injection). It can be abused to send out multiple spam emails by adding several email addresses to an injected CC:
or BCC:
header. Note that some mail programs replace \n
to \r\n
automatically.
In order to use the mail()
function in PHP, an email program or server has to be configured. The following two options can be used in the php.ini
configuration file:
When PHP is configured with the second option, calls to the mail()
function will result in the execution of the configured MTA program. Although PHP internally applies escapeshellcmd()
to the program call which prevents an injection of new shell commands, the 5th argument $additional_parameters
in mail()
allows the addition of new program arguments to the MTA. Thus, an attacker can append program flags which in some MTA’s enables the creation of a file with user-controlled content.
|
|
The code shown above is prone to a remote command execution that is easily overlooked. The GET parameter from is used unsanitized and allows an attacker to pass additional parameters to the mail program. For example, in sendmail, the parameter -O
can be used to reconfigure sendmail options and the parameter -X
specifies the location of a log file.
|
|
The proof of concept will drop a PHP shell in the web directory of the application. This file contains log information that can be tainted with PHP code. Thus, an attacker is able to execute arbitrary PHP code on the web server when accessing the rce.php file. You can find more information on how to exploit this issue in our blog post and here.
The 5th parameter is indeed used in a vulnerable way in many real-world applications. The following popular PHP applications were lately found to be affected, all by the same previously described security issue (mostly reported by Dawid Golunski).
Application | Version | Reference |
---|---|---|
Roundcube | <= 1.2.2 | CVE-2016-9920 |
MediaWiki | < 1.29 | Discussion |
PHPMailer | <= 5.2.18 | CVE-2016-10033 |
Zend Framework | < 2.4.11 | CVE-2016-10034 |
SwiftMailer | <= 5.4.5-DEV | CVE-2016-10074 |
SquirrelMail | <= 1.4.23 | CVE-2017-7692 |
Due to the integration of these affected libraries, other widely used applications, such as Wordpress, Joomla and Drupal, were partly affected as well.
PHP offers escapeshellcmd() and escapeshellarg() to secure user input used in system commands or arguments. Intuitively, the following PHP statement looks secure and prevents a break out of the -param1
parameter:
|
|
However, against all instincts, this statement is insecure when the program has other exploitable parameters. An attacker can break out of the -param1
parameter by injecting "foobar' -param2 payload "
. After both escapeshell*
functions processed this input, the following string will reach the system()
function.
|
|
As it can be seen from the executed command, the two nested escaping functions confuse the quoting and allow to append another parameter param2
.
PHP’s function mail()
internally uses the escapeshellcmd()
function in order to secure against command injection attacks. This is exactly why escapeshellarg()
does not prevent the attack when used for the 5th parameter of mail()
. The developers of Roundcube and PHPMailer implemented this faulty patch at first.
Another intuitive approach is to use PHP’s email filter in order to ensure that only a valid email address is used in the 5th parameter of mail()
.
|
|
However, not all characters that are necessary to exploit the security issue in mail()
are forbidden by this filter. It allows the usage of escaped whitespaces nested in double quotes. Due to the nature of the underlying regular expression it is possible to overlap single and double quotes and trick filter_var()
into thinking we are inside of double quotes, although mail()
s internal escapeshellcmd()
thinks we are not.
|
|
For the here given url-encoded input, the filter_var()
function returns true and rates the payload as a valid email address. This has a critical impact when using this function as a sole security measure: Similar as in our original attack, our malicious "email address" would cause sendmail to print the following error into our newly generated shell "@a.php
in our webroot.
<?=eval($_GET[c])?>\/): No such file or directory
Remember that filter_var()
is not appropriate to be used for user-input sanitization and was never designed for such cases, as it is too loose regarding several characters.
Carefully analyze the arguments of each call to mail()
in your application for the following conditions:
\r
and \n
characters are strippedIn fact, there is no guaranteed safe way to use user-supplied data on shell commands and you should not try your luck. In case your application does require user input in the 5th argument, a restrictive email filter can be applied that limits any input to a minimal set of characters, even though it breaks RFC compliance. We recommend to not trust any escaping or quoting routine as history has shown these functions can or will be broken, especially when used in different environments. An alternative approach is developed by Paul Buonopane and can be found here.
Many PHP applications send emails to their users, for example reminders and notifications. While email header injections are widely known, a remote command execution vulnerability is rarely considered when using mail()
. In this post, we have highlighted the risks of the 5th mail()
parameter and how to protect against attacks that can result in full server compromise. Make sure your application uses this built-in function safely!