PHPMailer Exploit – Remote Code Execution
legalhackers.com
legalhackers.com
mail ( string $to , string $subject , string $message [, string $additional_headers [, string $additional_parameters ]] )
it would be something like: mail ( string $to , string $subject , string $message [string $additional_headers], [string $additional_parameter, string $parameter values ] )
With the result passed to sendmail via the underlying functions, not relying on the shell to separate options for you. Any sort of user-supplied data should be parameterized and treated differently than data you provide. We've mostly learned our lessons from SQL injection, the rest of the stack still has a problem.PHPMailer (not core php) apparently either calls php's popen(), which passes to the shell...or calls php's mail(), which uses popen(). There are other options in php, like proc_open(), but they also call /bin/sh.
TLDR: There isn't any way in PHP to avoid "relying on the shell to separate options for you".
PHPMailer 5.2.17 sanitizes the $Sender variable
by applying escapeshellarg() escaping before the
value is passed to mail() function.
It does not however take into account the clashing
of the escapeshellarg() function with internal
escaping with escapeshellcmd() performed by mail()
function on the 5th parameter.
As a result it is possible to inject an extra quote
that does not get properly escaped and break out of
the escapeshellarg() protection applied by the patch
in PHPMailer 5.2.17.
In most cases, people are using PHP as a server-side language under a web server. In those cases, it really isn't a good idea to send mail in-process anyway due to web-process and SMTP timeouts. Queueing messages with something like Gearman or some other job handler would be a more secure and performant approach.Where?
Barring user-submitted comments, PHP has consistently had some of the most complete programming language documentation.
This exact issue is specifically documented with the mail() function[1] so I'm not sure you can blame PHP or ask for better documentation in this case:
"This parameter is escaped by escapeshellcmd() internally to prevent command execution. escapeshellcmd() prevents command execution, but allows to add additional parameters. For security reasons, it is recommended for the user to sanitize this parameter to avoid adding unwanted parameters to the shell command."
Escaping too many times or incorrectly is a pretty common error but it's normally of the PEBKAC variety.
So, yes, you can, but software like Wordpress includes a default configuration that works for most people without additional work...piping to /usr/bin/sendmail.
Why is sending a message to something like RabbitMQ less likely to timeout then to postfix?
Last I checked, PHP's mail() function blocks until SMTP connect/auth/submit completes, and with things like SMTP tarpitting, or just ordinary slowness, that can take a very long time.
Sometimes, it takes more time than your server-side web process is alloted for execution, leading to your script being force-terminated prior to completion.
[in reply to skarap] The action of mail() depends on the mail/sendmail settings in your php.ini, so the behavior could be blocking or non-blocking depending on which client is used and which parameters are passed.
In both cases it works like this:
client: MAIL FROM
server checks whether it makes sense (outgoing smarthost has essentially nothing to check here)
SMTP server: 250 Ok
client: RCPT TO
server checks, again there is nothing expensive to check for smarthost case
SMTP server: 250 Ok
client: DATA ... .
server does its checks on the contents of message (again, nothing meaningful for smarthos), reformats it, adds Received and such and writes it out into queue
SMTP server: 250 Ok: queued
server wakes up the process that handles delivery by IPC and gives it queue ID of just created message
client probably disconnects now
outgoing SMTP daemon starts connecting to somewhereWhen you only enqueue event such as "this happened, there should probably be an notification for that" and have some non-trivial application logic in the queue runner it starts to make sense. But queue runner of the kind for(;;) {msg = get_message(); smtp_send(message)} is complete nonsense.
By the way, I know of pretty significant line of bussiness system that has nothing to do with email except the fact that it uses smtp and postfix as it's message bus and the thing seems to just work without issue, for more than decade.
As for the jobber, it was using the same PHP mail() function, it's just that the jobber runs async and does not have a time limit imposed on it.
If you have control over your php.ini to where you can set agent parameters to be non-blocking, then yes, that would be ideal.
That is also the case when using the mail server at 127.0.0.1 port 25.
One could have configured a remote SMTP server with authentication and that would be slow and would be affected by network issues, but same would happen with a remote RabbitMQ server.
Edit: I should probably clarify that it is the 1 day timeline over Christmas that I think is irresponsible.
Edit: Seems like another exploit found 8 hours ago: https://github.com/PHPMailer/PHPMailer/issues/924
Probably wise to disable phpmailer on your servers for now.
It would seem much safer to establish a TCP connection to the local MTA over port 25 or 587 and send the message that way.
Admins can just use iptables to restrict access to the port to localhost, and/or do the same in their MTA config.
The best way I've found to send mail on behalf of someone else is to leave the return path (-f) and From header pointing to your own domain, and use the Reply-To header with the users email.
"To deliver electronic mail (email), applications shall support the interface provided by sendmail (described here). This interface shall be the default delivery method for applications."
http://refspecs.linux-foundation.org/LSB_3.0.0/LSB-PDA/LSB-P...
Last time I tried configuring a Linux-hosted PHP to do SMTP on localhost:25 instead of shelling out to call sendmail, I found that only Windows builds even have that functionality compiled in. You can apply the same configuration options on a stock Linux build, and they won't cause errors, but they won't do anything, either. Maybe that's changed recently, but it would have to have been very recently, because I ran into this (for the umpteenth time) just a few months ago, while reworking my team's dev environment to allow for examining sent mail without requiring heroism.
I'm not a PHP hater, exactly. I don't like the language at all, but I understand it well and have made a very good living based partly on that knowledge. But the mail story in PHP has never not been a dumpster fire.
PHPMailer 5.2.21 is out!
Wordpress is adding a fix, but I assume that's to cover plugins that allow end users to set the From: address, like perhaps "Share this with a friend" type functionality where the email is meant to look like it's from a different domain.
In short, I don't think most wordpress installations are remotely exploitable via this bug.
filter_var($email, FILTER_SANITIZE_EMAIL) works for this exploit, as it removes spaces and double quotes.
The SMTP plugins I surveyed still use PHPMailer.
You'd want to try something like:
/**
* Block the PHPMailer vulnerability:
* https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10045-Vuln-Patch-Bypass.html
*/
function example_wp_mail_filter($args) {
$new_wp_mail = array(
# Get rid of quotes in quoted emails: "bad stuff"@example.com. Should be
# sufficient sabotage.
'to' => preg_replace('[\'"]/u', "", $args['to']),
'subject' => $args['subject'],
'message' => $args['message'],
'headers' => $args['headers'],
'attachments' => $args['attachments'],
);
return $new_wp_mail;
}
add_filter('wp_mail', 'example_wp_mail_filter');(Also, if the SMTP plugin uses PhpMailer, but actually is configured to talk to SMTP, there is no mail() and the issue is moot)
What countries do you block..?
On one of my web-facing servers running Postfix:
root@:/home/rob# sendmail -X/home/rob/test.log
sendmail: fatal: unsupported: -X/Php's implementation of popen() doesn't invoke commands directly with stdlib's execl() or execle()...it calls stdlib's popen(), which passes it through /bin/sh -c <cmd>.
That's assuming phpmailer doesn't treat a postfix sendmail wrapper differently than sendmail.
Edit: As far as I can tell, php doesn't allow any way to spawn a process without passing it to /bin/sh. That's odd, as other script languages like Perl and Python provide that. It avoids a whole class of exploits.
Edit2: It seems to be exploiting the "-f" option, not "-X". The postfix wrapper supports that.
I don't see a whole lot that a non-root user calling Postfix's version of sendmail can do with this ... maybe get a mail server RBL'd.
Header Field: From
Specifies the author(s) of the message; that is, the mailbox(es)
of the person(s) or system(s) responsible for the writing of the
message. Defined as standard by RFC 822.
Header Field: Sender
Specifies the mailbox of the agent responsible for the actual
transmission of the message. Defined as standard by RFC 822.
As far as I understand, this means that yes, you should set the From address to the user's email when sending content submitted by that user. In contrast, you should never set the Sender address to any user's email.Another discussion is whether this is sensible or not, how well it works delivery-wise, etc... In any case, it is not as clear-cut as you make it seem.
$headers = "From: noreply@yourwebsite.com\n";
If you want to be able to reply to the email you can still set it with something like $headers .= "Reply-To: $email_address";
Then in your content body you could also include your $email_address variable.The real issue to me is that unlike Perl and Python, PHP doesn't provide a way to spawn a process without invoking /bin/sh. If PHP had support for the various incantations of exec(), you could pass arguments without needing the shell.
[1]: Such as https://wordpress.org/plugins/wp-mail-smtp/
But then again, why would you send e-mail to just any address someone enters, without validating it for correctness? I know it is tough to validate all addresses according to RFC, but I'd rather block some legitimate users (which btw. probably know more about RFC than me and I'm sure can find a "nicer" e-mail address if they want to) than let some attacker use some vulnerability like this. Always whitelist valid input, never (just) blacklist it.
Also, I am baffled that frameworks I encountered never demanded from developer to specify exactly what kind of input it expects via POST & co.. It is trivial to write a set of functions like this:
function input_post_email($field_name, $default_value)
function input_post_string($field_name, $validation_regex, $default_value)
...
The point here is that framework should DEMAND from developer to specify format of each and every input var it needs. It should be difficult to bypass these restrictions, to demotivate developers doing it.This is a first thing I made in every PHP project I started. Combined with Content Security Policy and output filtering it's... well, better than most other solutions. :)
It's also why I far-too-often run into forms that won't let me use a "+" in the username part of my email address, which I use to track who's responsible for sending my email account off to third parties (e.g., "rob+paypal@....").
Some kinds of email validation are better than others. Using regular expressions and strictly adhering to the RFC is the one that developers are usually talking about when they say not to do it. filter_var(..., FILTER_VALIDATE_EMAIL) is sort of okay, although there are lots of edge cases that it doesn't handle correctly.
Other than that: true, I hate incorrect validation. But I hate sloppy security practices even more.
To get around that, the service provider would have to verify identity further down the line, making the email format restriction redundant.
If the service value is not too high then even making people who want to abuse the service go through the process of registering a free email account works. Put a little bit of friction into the process and the script kiddies move onto an easier target.
I don't have any other email. Seems like a really bad idea.
In general I agree that PHP mail() function should take a part of blame here for not exposing a sane interface. Spammers have abused many bugs where programmers failed to sanitize "To:" or "From:" addresses and attackers could pass \r and \n characters (which allowed adding BCC fields, effectively sending e-mails to arbitrary addresses).
> Also the vuln does not apply to CLI sendmail...
Sure it does: >> ...will result in the followig list of arguments passed to sendmail program:
EDIT: you probably meant to say that the vulnerability is only triggered when CLI sendmail is called via mail(), which is true. Configuring PHPMail to use sendmail via CLI directly apparently doesn't expose this vulnerability.
you have to be careful with the meaning of "valid" though: What's valid in one context might not be in another context. Do you want to limit the character set to the least common denominator of non-special characters valid in all possible contexts your address might be used?
What if you don't exclude some character because it's not special in any of the current contexts but then you later add another thing to the mix that uses in-band signalling? Now you need to update your whitelist and remove even more "invalid" characters.
This won't end well for you.
I would recommend you don't put a restriction on the input (aside of what the RFC defines as valid) and instead correctly escape (or throw if your context doesn't support escaping) when moving the raw data to a new context.
In this case, I think the problem is in PHP's `mail()` function that put stuff in shell context without any escaping.
The fix should be happing in `mail()` (which is internally shelling out and thus switching context), not in the caller and certainly not in the frontend controller when it needs to decide whether a given email address is valid or not.
You should still properly escape data when passed to another context, no doubt about it.
I am also not suggesting "dirty" practices like replacing double quotes (Wordpress) or magically escaping quotes on input (PHP prior to... 5?). But if you know the input should be a number, allow only digits and '.' and clean everything else. And validate the range too! If you need a database ID, validate form and existence. If you need an e-mail address, run it through filter_var. Always. There is no reason not to.
That's not correct. PHPMailer can be configured to send mail through raw SMTP, by directly invoking sendmail, or by calling PHP's mail() function (which is itself a wrapper around sendmail). This vulnerability affects only the last mode, when PHP's mail() calls sendmail. If you have PHPMailer configured to call sendmail directly, this vulnerability does not apply.
Understatement of the year!
If you were to try and actually validate according to the RFC, chances are you'll introduce new vectors of attack in your code simply because of the complexity of what you're trying to achieve.
Honestly, the email address RFC is a litany of insane choices and kludged-together standards to account for the wild west free-for-all that existed before the modern internet emerged, and most of it has no bearing on the reality of how people use and create email addresses today. Better to just check if the address supplied has an @ in it somewhere and be done with it.
So yes, I believe that in 2016, a match on ^[^@]+@([^.@]+\.)+[^.@]+\.?$ does not have any actual false negatives, although allowing false positives. That's not entirely helpful in this CVE, though.
filter_var($app->request()->post('email'), FILTER_VALIDATE_EMAIL)