Advertisement






Magento – Unauthenticated Remote Code Execution

CVE Category Price Severity
CVE-2016-4010 CWE-287 $2,000 Critical
Author Risk Exploitation Type Date
ExploitAlert Team Critical Remote 2016-05-18
CVSS EPSS EPSSP
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H 0.02192 0.50148

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2016050087

Below is a copy:

Magento  Unauthenticated Remote Code ExecutionThe vulnerability (CVE-2016-4010) allows an attacker to execute PHP code at the vulnerable Magento server unauthenticated. This vulnerability actually consists of many small vulnerabilities, as described further in the blog post.

Magento is an extremely popular eCommerce platform purchased by eBay at 2011.
It is used by major corporations, such as Samsung, Nike and Lenovo, and by small online merchants alike. All in all, Magento is used in 250,000 online shops, handling approximately 60 Billion dollars a year.
These statistics, along with the fact Magento stores almost all customer information, makes it an extremely sensitive target, and the main reason I audited it once more.


The vulnerability assumes one of the RPCs (REST or SOAP) is enabled. As both are enabled by default, and one of them is actually required by the system, this assumption will not be a problem in the absolute majority of installations.
In this document I will use the SOAP API, as XML is more readable in this case.

This vulnerability works on both the Community Edition and Enterprise Edition of the system.
I recommend all Magento administrators to update their installations to the 2.0.6 patch.

 

Vulnerable Versions

Magento EE & CE before 2.0.6.

 

Technical Description

The Good

Since the last time I audited Magentos code, a lot has changed. Most of the code has been re-written, the directories structure changed, and the security mechanisms improved.
So, naturally, I was quite happy auditing this massive codebase again.

The first thing I noticed while going through the code was the vast improvement to the systems OOP structure. Almost every class in the system implemented at least one interface, inherited from a parent class, and used some advanced magic-method, such as __call() or __get() to better expose private properties.

Of course, while being a huge step-forward, this extreme example of OOP implementation also complicated things quite a bit  both for me as a researcher, and for Magento developers.
Now, when a developer is writing a new class, it is forced to inherit, or implement, a different class or interface, written by another developer. While this happens quite a lot in the software development world, the security implications of this work-methodology has greatly increased in this particular system  a result of the very dynamic code flow, and, unfortunately, the very dynamic API.

Magento, at its core, is a system built by different modules. These so called modules are basically different directories containing code for different functionalities the system offers. For example, theres a module responsible for the payment, one thats responsible for the virtual cart and one responsible for the customers.

In each module theres a special directory named API. This directory contains all the functionality the module exports to other modules. That way the payment module can communicate with the cart module, the customer module with the authorization module, or the shipping module with the sales module, naming a few examples.

The API directory is made out of different PHP files, each containing one PHP class, responsible for exposing some of the module functionality to the rest of the system. Most of the modules API functionality is restricted to other system controlled modules only, as most of it is sensitive.
But, some of the API calls has also been made accessible to another, more user controlled, module  the notorious Web API.

Magentos Web API is allowing two different RPCs  a REST RPC, and a SOAP API. Both RPCs provide the same functionality, the only difference between the two is that one is using JSON and the HTTP query string to transfer its input, while the other uses XML envelopes.
As both are enabled by default, I will use SOAP API in this document as I find it more understandable.

 

The Bad

Without a doubt in my mind, I can safely say Magento features one of the most comprehensive APIs I have ever seen in a PHP CMS, or really any CMS at all.

In order to expose just part of each modules API, Magento has provided modules developers a convenient way of declaring which parts of their modules API they want to make accessible to the Web API  the webapi.xml file.

The webapi.xml file contains all the classes and methods exposed to the Web API, in a neat, organized, XML structure. Each exposed method also specifies the specific privilege level it requires.
These privileges can range from anonymous  allowing anyone (even guests) to access the method, to self  allowing access only to registered customers, and to specific, admin-only, privileges, such as the Magento_Backend::admin privilege  allowing access only to administrators with capabilities of editing the server configuration values.

While granting modules developers a convenient way of communicating between the frontend of the system and its backend, the Web API, using the webapi.xml file, also opens another door leading directly into the modules core.

As we will assume the role of guests in the store, we will only be able to access methods requiring the anonymous privilege. This will greatly narrow our attack surface, but will remove the requirement for any special pre-conditions.

Interestingly enough, even with anonymous privileges we could still use very dynamic input types. Im not referring to the regular XMLRPC input types alone, such as arrays or base64 decoded strings, Im also referring to different objects available in the system. For example, the CustomerRepositoryInterface::save() API function allows us to use a CustomerInterface object in the $customer variable, as can be seen in the following prototype:

interface CustomerRepositoryInterface
{
    /**
     * Create customer.
     */
    public function save(MagentoCustomerApiDataCustomerInterface $customer);
 
}
How can objects be created using the RPC interface? Interestingly enough, the answer to that question relies in how Magento configured their SOAP server.

Magento uses the default SOAP server that is bundled by default with PHP  SoapServer. In order to be properly configured, SoapServer requires a WSDL file, which describes all the methods, arguments, and custom types used in the particular RPC request.
Magento generates a different WSDL file for every module supporting XMLRPC functionality, setting its data directly from the modules webapi.xml file.

When an RPC request is parsed by the server, the server uses the data found in the WSDL file to decide whether the request is valid or not, checking the requested method, its arguments, and their types. If the request is valid, it passes the parsed request object back to Magento for further parsing.
It is important to note that SoapServer does not interact with Magento in any way  all the information it has about the modules methods and arguments comes from the WSDL file alone.

At this point, the request sent is still made out of nested arrays  no objects were created during SoapServers parsing phase.
In order to create the required objects, Magento continues to handle the input itself.

Magento obtains the prototype (can be seen in the previous code example) of the method requested in order to extract its argument names and data types.
For basic cases, such as strings, arrays, booleans and so on, the system just casts the input into the appropriate type. But for objects, the solution is a bit trickier.

If the argument data type is an instance of a class, Magento will try and create that instance using the supplied input. Remember that at this point the input is just a dictionary  its keys are the properties names and values are the properties values.

First, Magneto will create a new instance of the required class. Then, it will try and populate it using the following algorithm:

Get a property name (from the input dictionary keys)
Look for a public method named Set[NAME](), where [NAME] is the property name
If there is such a method, execute it using the property value as an argument.
If there isnt, ignore the property and continue to the next one
Magento will follow this algorithm for every potential property the user is trying to set. When all properties have been checked, Magento will assume the instance is ready and it will move to the next argument.
When all arguments are dealt with, Magento will then finally execute the API method.

Let me clarify that part  Magento lets you create an object, set its public properties, and execute any method starting with the Set prefix through its RPC.
Surprisingly, this behavior will be Magentos downfall.

 

The ugly

Some API calls allow us to set specific information in our shopping cart. Such information can be our shipping address, products, and even our payment method.
When Magento sets our information safely inside the shopping cart instance, it uses the save function of that instance in order to store the newly inserted data in the database.

Lets have a look at how the save function looks like:

/**
 * Save object data
 */
public function save(MagentoFrameworkModelAbstractModel $object)
{
    ...
    // If the object is valid and can be saved
    if ($object->isSaveAllowed()) {
        // Serialize whatever fields need serializing
        $this->_serializeFields($object);
        ...
        // If the object already exists in the DB, update it
        if ($this->isObjectNotNew($object)) {
            $this->updateObject($object);
        // Otherwise, create a new record
        } else {
            $this->saveNewObject($object);
        }
         
        // Unserialize the fields we serialized
        $this->unserializeFields($object);
    }
    ...
    return $this;
}
 
// AbstractDb::save()
Magento makes sure our object is valid, serializes any fields that should be serialized, stores it in the database, and finally it unserialize the fields it previously serialized.

Sounds simple, right?

Nope.

Lets have a look at how Magento decides which fields it should serialize:

/**
 * Serialize serializable fields of the object
 */
protected function _serializeFields(MagentoFrameworkModelAbstractModel $object)
{
    // Loops through the '_serializableFields' property
    // (containing hardcoded fields that should be serialized)
    foreach ($this->_serializableFields as $field => $parameters) {
        // Get the field's value
        $value = $object->getData($field);
         
        // If it's an array or an object, serialize it
        if (is_array($value) || is_object($value)) {
            $object->setData($field, serialize($value));
        }
    }
}
 
// AbstractDb::_serializeFields()

As can be seen, only fields appearing in the hardcoded dictionary _serializableFields can be serialized. On top of that, the method makes sure the fields value is an array or an object before it continues to serialize it.

Now, lets have a look at how Magento decided which fields it should unserialize:

/**
 * Unserialize serializeable object fields
 */
public function unserializeFields(MagentoFrameworkModelAbstractModel $object)
{
    // Loops through the '_serializableFields' property
    // (containing hardcoded fields that should be serialized)
    foreach ($this->_serializableFields as $field => $parameters) {
        // Get the field's value
        $value = $object->getData($field);
         
        // If it's not an array or an object, unserialize it
        if (!is_array($value) && !is_object($value)) {
            $object->setData($field, unserialize($value));
        }
    }
}
 
// AbstractDb::unserializeFields ()
Well, its pretty much the same. The only difference is that this time Magento makes sure the fields value is not an array or an object.
Because of these two checks, we could potentially exploit an object injection attack  simply by setting a regular string to a serializable field.

When we set a regular string to a serializable field, the system will not serialize it before the object is stored in the database, as it is not an object or an array. But when the system will try to unserialize it, right after the database queries has been executed, it will be unserialized, as it is not an object or an array.

This small, almost invisible, condition, makes the difference between a vulnerable and a secure system.
The only questions remaining are which fields considered serializable, and how do we set them.

The first question is easy  we just need to search which classes define the _serializableFields property.
Quickly enough, I found several classes that define serializable fields, but none of them could be created using our beloved XMLRPC.

To be honest, one of these classes  Payment (which is responsible for gathering information about the payment details), does appear in one of the API methods, but not as an argument, so I couldnt create or control its instance properties.
On top of that, its serializable field  additional_information, can only be set to an array using the regular Set[PROPERTY_NAME] technique as an extra security measure, so not only we cant create it, we wouldnt be able to set it as a string even if we could.

Interestingly enough, it could be set in another tricky way.

When Magento sets the properties of the argument instance, it doesnt actually set its properties. Instead, it stores them in a dictionary named _data. This dictionary is then being used in almost all cases where an instance property is needed.
In our case, this means that our serializable field  additional_information, is actually being stored inside that dictionary rather than as a regular property.

As a result, if we could control the _data dictionary entirely, we could bypass the additional_information field array restriction, as we will set it manually rather than call Set[PROPERTY_NAME].
But how can we control this sensitive dictionary?

Well, one of the things Magento does before saving our Payment instance is to edit its properties. Magento does that by treating our API input as payment information thats needed to be stored in the Payment instance. This can be seen in the following API method:

/**
 * Adds a specified payment method to a specified shopping cart.
 */
public function set($cartId, MagentoQuoteApiDataPaymentInterface $method)
{
     
    $quote = $this->quoteRepository->get($cartId); // Get the cart instance
    $payment = $quote->getPayment(); // Get the payment instance
 
    // Get the data from the user input
    $data = $method->getData();
     
    // Check for additional data
    if (isset($data['additional_data'])) {
        $data = array_merge($data, (array)$data['additional_data']);
        unset($data['additional_data']);
    }
     
    // Import the user input to the Payment instance
    $payment->importData($data);
     
    ...
}
 
// PaymentMethodManagement::set()

As can be seen, the Payment data is retrieved with a call to $method->getData(), which returns the _data property from the $method variable. Keep in mind that because $method is an argument in an API method, we are in control of it.

When Magneto calls getData() on our $method argument, the _data property of that argument is returned, containing all the payment information we inserted.

Later, it calls importData() with our _data property as input, replacing the Payment instance _data property with our _data property.

This is a huge step forward. We can now replace the sensitive _data property in the Payment instance with our user controlled _data property, which means we can now set the additional_information field.

The problem is that for our unserialize() to work we need that field set to a string, but the Set[PROPERT_NAME] method only allows it to be an array.

Surprisingly, though, the solution lies two lines of code before the importData() call.
Magento, in its constant effort of becoming the most dynamic CMS ever created, allows developers to add their own payment methods, requiring their own data and information. In order to do that, Magento uses the additional_data field.

The additional_data field is a dictionary containing more data for the payment method, completely controlled by the user. In order for that custom data to be part of the original data, Magento merges the additional_data dictionary with the original data dictionary, affectively allowing the additional_data dictionary to override any value in the data dictionary, basically letting it completely override it.

This means that now, after the two dictionaries merged, the user controlled additional_data dictionary now became the arguments _data dictionary, and because of importData(), it now becomes the Payment instance sensitive _data property.
Which means, we can now completely control the serializable field additional_information, and exploit an Object Injection Attack.

 

And the unserializable

Now, that we can unserialize any string we wanted, its time to exploit the Object Injection.

First, we will need an object with a __wakeup() or __destruct() method, so it will be automatically called when the object is unserialized or destroyed. We require such methods because even though we can control the object properties, we cant call any of its methods. This is why we have to rely on PHPs magical methods, which are called automatically when certain events occur.

The first object we will use will be an instance of the Credis_Client class, which contains these methods:

/*
 * Called automaticlly when the object is destrotyed.
 */
public function __destruct()
{
    if ($this->closeOnDestruct) {
        $this->close();
    }
}
 
/*
 * Closes the redis stream.
 */
public function close()
{
    if ($this->connected && ! $this->persistent) {
            ...
            $result = $this->redis->close();
    }
    ...
}
 
// Credis_Client::__destruct(), close()

As can be seen, the class does have a simple __destruct() method (that will be automatically called by PHP when the object is destroyed), which simply calls the close() method.
close(), on the other hand, is more interesting  if close() recognizes that there is an active connection to a Redis server, it tries to close it by calling the close() method on the redis property.

Because unserialize() allows us to control all the object properties, we control the redis property too. Because of that, we can set any object wed like into that property (not just a Redis one), and affectively, call any close() method in any class in the system.

This greatly expands our attack surface. There are several close() methods in Magento, and because close() methods are usually responsible for terminating streams, closing file handles, and storing object data, we can expect some interesting calls in some of them.
And, as expected, I did find some interesting calls. Lets have a look at the close() method of the Transaction class:

/**
 * Close this transaction
 */
public function close($shouldSave = true)
{
    ...
    if ($shouldSave) {
        $this->save();
    }
    ...
}
 
/**
 * Save object data
 */
public function save()
{
    $this->_getResource()->save($this);
    return $this;
}
 
// MagentoSalesModelOrderPaymentTransaction::__destruct(), close()

Both of these methods are pretty simple. The close() method calls the save() method, which then calls the save() method of the _resource property.
Using the same logic we used before, because we control the _resource property we can control its class too, so we can call any save() method in any class wed like.

Thats a huge step forward. As can be guessed, save() methods are usually responsible for storing some kind of data in some kind of storage  the file system, a database, and so on. All we have to do now is look for a save() method which uses the file system as its storage utility.

And, quickly enough, I found one:

/**
 * Try to save configuration cache to file
 */
public function save()
{
    ...
    // save stats
    file_put_contents($this->getStatFileName(), $this->getComponents());
    ...
}
 
// MagentoFrameworkSimplexmlConfigCacheFile::save()

All this method does is store what is inside the components field into a file. As the file path is retrieved from the stat_file_name field, and because we control these two fields, we effectively control both the file path and its content, which results in an arbitrary file write vulnerability.

All we have to do now is find a good path to write our file in, one that has to be writeable by the server and accessible via the web.
One such path is the /pub directory, available in all Magento installations. Magento uses that directory in order to store any images or public files an administrator uploaded, and as such, it has to be writeable by the server.
More importantly, because that directory stores mostly images, which needs to be retrieved by the browser, it is accessible through the regular web interface.

Now we need to write some PHP code, give our file name a .php extension, and were good to go. Weve executed arbitrary PHP code on the server unauthenticated.


Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum