Attacking PHP’s mail() function in Magento

For those who host Magento and have to apply security patches released on an ad-hoc basis such things can be disruptive and some what mystifying. There you are steadily developing and improving your site as planned when with out warning you must drop everything and patch the live code before some unspecified bad thing happens.

A quick check with magereport.com reveals many sites are slow to apply patches. I believe by looking at potential exploits and trying them out we can better understand the intention of the patches and the risks that an un-patched site exposes.

Magento patch SUPEE-9652 was a very small change. Only a single source file was altered with only a few lines of code changed.

The patch was to prevent a specific attack on PHP’s mail() function which in some circumstances allowed an attacker to execute PHP code on a site whilst only authenticated as a customer.

Forgotten Feature

In Magento 1.X, one area where a user can send an email is from the “Send a Product to a Friend” functionality. The use of this feature is configurable but turned on by default. Often custom designs will remove the link from the product page itself in favour of social media buttons but you can still navigate to the url.

Screen Shot 2017-06-13 at 14.48.41
Email a product recommendation to a friend form

“Send a Product to a Friend” is meant to allow customers to send an email to a friend about a product they think they may be interested in. To prevent abuse it is normally only for logged in customers and the admin can set limits on the number of emails sent per hour etc.

By default Magento uses the supplied email address to set the From: header on the sent email only. However, within the Magento admin backend is a setting labelled  ‘Set Return-Path’ this does indeed set the return path header in email. However it also sets the sender envelope address which is sent to PHP’s mail() function via a -f flag passed directly to sendmail.

Screen Shot 2017-06-13 at 14.51.51
The “Set Return-Path” option which should always be set to No

Code from app/code/core/Mage/Core/Model/Email/Template.php

if ($returnPathEmail !== null) {
 $mailTransport = new Zend_Mail_Transport_Sendmail("-f".$returnPathEmail);
 Zend_Mail::setDefaultTransport($mailTransport);
 }

Late in 2016 Dawid Golunski disclosed vulnerabilities in several wrapper scripts for PHP’s mail function which used the from address to set the sender address on sendmail. The problem comes that several valid forms of email address can be used to escape the sendmail command and set additional flags on the sendmail operation. Sendmail arguments are separated by spaces ” “.

Both the from email addresses and that of the recipient are validated as being of a valid format.
However, the local part of an email address in particular can contain more than is normally expected and still be valid according to the specification. There are several valid email address formats that are surprising such as

"dave"@example.com
!dave!xyz%abc@example.com

This quoted local name format crucially can contain spaces which are the way sendmail arguments are generally separated.

This means it is possible for an attacker to add extra sendmail parameters which then get sent to the command line sendmail and perform actions as well as attempting to send email. One flag that can cause extra behaviour is the -X log flag which writes out to a log file at a path specified as an argument. It is worth noting that this flag will only work if the target server is running sendmail and not postfix’s sendmail compatibility interface or any other Message Transfer Agent (MTA). Postfix will, for instance, accept the -X log flag to maintain compatibility but ignore it.

My Old Friend PHP

The -X flag allows the caller to specify the location and name of the log file. If it is named with a PHP extension and placed somewhere in the web root then the attacker can navigate to the log file and any PHP will be executed.

e.g. "dave\" -Xmedia/log.php  some"@test.com

Therefore in order to get our code running on the remote server we need some way of putting it within the email that would be sent. One thing that makes this attack, marginally, more difficult (but more fun) is that the users message is escaped before being included in the email and hence log file. This turns any “<” characters, which are necessary for PHP code tags, into their html equivalents &lt;  This means it is not possible to include executable PHP code as part of the message body itself. However, the sendmail log file format helpfully quotes the email address in “<” and also removes the double quotes.

Starting an email address “?php will then create a valid PHP tag when shown in the log file.

For example an email address of “?php echo ‘hello’; “@example.com  will get converted into <?php echo ‘hello’; @example.com>

A PHP closing tag cannot be created by the same method however. The email address validation will not accept a domain ending in a “?” character and so the logfile quoting there is no way to form a closing PHP tag “?>” tag for us.

The last piece of the puzzle is the __halt_compiler() PHP function. This means that PHP code parsing will end and the rest of the log file does not need to be valid PHP code. This is important since in PHP complete parsing of syntax happens before any code is executed.

So to get this working an attacker uses a sender email with the -X flag and path.

e.g.

"d\" -oQ/tmp/ -Xmedia/log.php some"@test.com

And then includes there PHP code in the recipient email address.

"?php readfile('/etc/passwd'); __halt_compiler(); "@test.com

In this case the action is just to read the contents of /etc/passwd however a malicious attacker is more likely to use it to upload a web shell.

The Patch

The fix applied by Magento as patch  SUPEE-9652 was to add email validation to the parameters before calling PHP’s mail() function this prevents email address of the form “dave”@example.com being used since the -f flag is already prefixed and -f”dave”@example.com fails validation. This effectively means Magento can not send an emails to addresses of the quoted format. However, these were normally prevented from being used by client side javaScript so it is unlikely they would be legitimate. The original attack relied on the server sending email via the relatively uncommon sendmail program. By preventing the use of the quoted format and email address containing spaces this has also prevented further attacks which have been published by sending extra parameters to exim.

The Strange Tale of the Frog Images

If you have been maintaining a Magento 1.X store for the last year or so you may well recognise this image.

image

If you do a reverse image search and include the path to Magento’s media folder you get some interesting results.

Screen Shot 2017-06-12 at 12.50.29
A google image search for the pepe the frog image with the string “Index of /media/catalog/category”

You can see this image appearing under different names in the /media/catalog/category folder of dozens of Magento 1.X sites. What is more it has been uploaded without the knowledge of the site owners. But why is it a case of simple vandalism?

If take a closer look at the image on one of these sites you can see PHP code appended to the binary image data.

Screen Shot 2017-06-12 at 12.55.42

The file is an attempt to execute malicious code on the web server hosting the sites. In this case it seems to be trying to write a backdoor PHP script to a file located at /skin/skins.php and another to skin/test1.php but the images contain a variety of payloads.

Loading the image for display as we just did will not execute the PHP code. Instead it outputs the uninterpreted code which is how we were able to see it.

How are such files executed by the attacker?

There is a clue in the code in the image. It searches for index.php in the SCRIPT_FILENAME global variable to find the scripts path. It therefore looks like the script is being executed via a request to the frontend or admin side of the store and not through any other path. It is almost as if the image is being included as a PHP source file.

I have found one method that is a likely one used to execute the code through the admin site. This means an attacker must already have a valid login for your admin site. If you are wondering how this could happen See this earlier post. Using an admin user the attacker is first able to save the image as the header image to one the existing categories. This will transfer the file to the location media/catalog/category on the target server.

The method will no longer work on up to date Magento installs since it makes use of vulnerabilities fixed by the latest security patch for Magento SUPEE-9767.

I believe that the image is being included by the attacker as part of a newsletter template. In an un-patched version of Magento you can indeed achieve execution of PHP code in images via this method. You simply use the image name as a template file when adding content to a newsletter template.

{{block type="core/template" name="test" template="../../../../../../media/catalog/category/evil.jpg"}}
Screen Shot 2017-06-13 at 11.58.56
Including an evil image as a template in a newsletter template

You then need to preview your newsletter template and hey presto your PHP code is executed!

Screen Shot 2017-06-12 at 13.35.11
Binary image data outputted to the screen with the output of PHP code at the end

In order for the path used above to work in the template there needs to be an non-default setting for the configuration setting:

dev/template/allow_symlink 

When set to 1 this negates a check in the code that only allows templates from the template directory to be included. The check uses PHP’s realpath function and hence breaks if the template path is a symbolic link.

Without the patch SUPEE-9767 the altering of this setting was available to an attacker who had successful gained access to the admin site.

Screen Shot 2017-06-13 at 11.57.18
The “Allow Symlinks” setting has been removed from the admin site now and is now configured in config.xml

This attack has been used in the wild on stores for several months. The solution which has been implemented in SUPEE-9767 was several fold. Firstly a filter was implemented that will strip PHP code from images uploaded. This uses tried and tested methods and should be reliable.

The second part of the fix was to remove the ability to skip the path check based on a setting in the admin in panel. Instead if you need templates to be included that contain symbolic links this setting is now in config.xml

Screen Shot 2017-06-13 at 12.24.45

This means that having gained access to the admin site an attacker can no longer update the setting and must have access to the filesystem.

So patch, patch, patch as soon as possible.

How Magento’s Authentication is Brute Forced

Easily the most common way I see Magento sites attacked is by the brute forcing of admin passwords. Attackers use automated scripts to try common usernames and passwords several times a second 24 hours a day 7 days a week to attack stores.

How do I know this?

Well I see it in the web access logs of Magento stores all the time.

Here is an example from the web access log of a real Magento store;

 Cimarron [04/Apr/2017:15:28:23 +0100] "GET /rss/catalog/notifystock/ HTTP/1.0 "301 184 "-" "-" "188.120.236.172"[RT:0.000] [C:22]
 Elijah [04/Apr/2017:15:28:37 +0100] "GET /rss/catalog/notifystock/ HTTP/1.0 "301 184 "-" "-" "188.120.234.207"[RT:0.000] [C:62]
 Kameron [04/Apr/2017:15:28:39 +0100] "GET /rss/catalog/notifystock/ HTTP/1.0 "301 184 "-" "-" "188.120.234.230"[RT:0.001] [C:67]
 RUIZ [04/Apr/2017:15:28:43 +0100] "GET /rss/catalog/notifystock/ HTTP/1.0 "301 184 "-" "-" "188.120.224.119"[RT:0.001] [C:70]
 Sullivan [04/Apr/2017:15:28:46 +0100] "GET /rss/catalog/notifystock/ HTTP/1.0 "301 184 "-" "-" "188.120.236.159"[RT:0.000] [C:73]
 markus [04/Apr/2017:15:28:51 +0100] "GET /rss/catalog/notifystock/ HTTP/1.0 "301 184 "-" "-" "188.120.232.68"[RT:0.000] [C:85]
 Rafael [04/Apr/2017:15:29:00 +0100] "GET /rss/catalog/notifystock/ HTTP/1.0 "301 184 "-" "-" "188.120.235.19"[RT:0.000] [C:99]
 Lillie [04/Apr/2017:15:29:02 +0100] "GET /rss/catalog/notifystock/ HTTP/1.0 "301 184 "-" "-" "188.120.236.155"[RT:0.000] [C:103]
 Elijah [04/Apr/2017:15:29:04 +0100] "GET /rss/catalog/notifystock/ HTTP

As you can see the attackers are making GET requests to /rss/catalog/notifystock/ . You can also see the usernames the attacker is trying in the first field e eg. Cimarron, Elijah, Kameron .

RSS Auth.
An example of what you see on visiting the rss/catalog/notifystock/ url. The required auth credentials are the same as the ones you log into the admin panel with.

This URL is among several which by default require basic HTTP auth. If you don’t remember setting up basic auth for this that is because you didn’t. The credentials used here are the same as those used in the main Magento login panel. That means that if an attacker can guess a single weak username and password from this url they can then go on to use the same credentials and even the same session in the admin backend.

But I hid my admin backend

If you hid the location of the backend, as you should, that won’t help you much as the location is revealed in the page that loads following a successful authentication.

Screen Shot 2017-06-13 at 09.01.14
An example of what an attacker would see following a successful authentication. Notice the hidden admin url is revealed.

As well as the /rss/catalog/notifystock/ url there are several others that use this basic auth method.
Such as:

  • rss/catalog/review
  • /rss/order/new
  • /index.php/rss/catalog/review
  • /index.php/rss/catalog/notifystock

 

Excuse me, your auth is showing

I am willing to bet that if you have not secured these urls then you will be receiving hundreds of automated requests on them a day. How confident are you in everyone of your admin users passwords?

Furthermore all these requests can be a significant resource drain on a small website since they will not be cached and will do at the very least one database lookup and password hashing.

What to do?

A while ago Magento warned about the use of these urls and suggested using an IP whitelist to block access for all but pre-arranged users. But I still see many sites with these urls accepting basic auth credentials from remote untrusted IP addresses. Possibly this is because Magento did not go out of their way to highlight exactly what the risk is and included it with advice on hiding the admin url which is well known.

If you don’t know for sure that you are using the rss feeds I would suggest that you simply set the web server to return a 404 not found error for these urls. If you do this the automated requests will stop completely within about 10 minutes. This makes sense since the attackers will want to save resources too.