Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Magento-CVE-2016-4010 — Magento Unauthorized Remote Code Execution (CVE-2016-4010) | Kitploit
Tools/GitHubGitHub/brianwrf/magento-cve-2016-4010
Vulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationLabs & Practice
GitHubbrianwrf/magento-cve-2016-4010

Magento-CVE-2016-4010

Magento Unauthorized Remote Code Execution (CVE-2016-4010)

View Repository
63210 years agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Analysis and Exploitation of Magento Unauthenticated Remote Code Execution Vulnerability (CVE-2016-4010)


0x00 Preface

On May 17, foreign security researcher Netanel Rubin publicly disclosed an unauthenticated remote code execution vulnerability in Magento (CVE-2016-4010). This vulnerability actually consists of multiple smaller flaws and allows an attacker to execute arbitrary PHP code on a vulnerable Magento server without authentication. Magento is a very popular e-commerce platform, acquired by eBay in 2011. Some well-known companies such as Samsung, Nikon, Lenovo, and many small e-commerce sites use it. It is reported that Magento is used by over 250,000 online stores, handling transactions worth approximately $60 billion annually.

0x01 Analysis

Prerequisites for exploiting this vulnerability:

  • Magento has RPCs enabled (REST or SOAP), which are enabled by default in most cases.
  • Magento CE & EE versions < 2.0.6

Magento's web API allows two types of RPCs: REST RPC and SOAP API. Both offer the same functionality, with the only difference being that the former uses JSON and HTTP requests to pass input, while the latter uses XML.

To expose only certain module APIs, Magento provides developers with a convenient method: declare only the APIs they want to make accessible in the "webapi.xml" file. The webapi.xml file contains all the classes and methods for the Web APIs that need to be exposed, and each method also specifies the required permission. These permissions include:

  • anonymous – Methods accessible to anyone.
  • self – Only accessible to registered users and specific admin permissions, e.g., "Magento_Backend::admin" permission only allows administrators who can edit server configuration to access.

Of course, this approach, which allows developers to use the webapi.xml file to communicate between the system's frontend and backend (Web API), essentially opens a backdoor directly into the module core.

Additionally, even with "anonymous" permission, we still need a way to dynamically pass values. For example, the "CustomerRepositoryInterface::save()" API function allows us to use a "CustomerInterface" object in the "$customer" variable. The code prototype is as follows:

root@kitploit:~
interface CustomerRepositoryInterface
{
/**
 * Create customer.
 */
public function save(\Magento\Customer\Api\Data\CustomerInterface $customer);
}

So how can we create an object using the RPC interface? In fact, the answer lies in how Magento configures the SOAP server.

Magento uses the PHP "SoapServer" bundled by default. To be properly configured, "SoapServer" requires a WSDL file that defines all methods, parameters, and custom types used in actual RPC requests. Magento generates different WSDL files for each module that supports XMLRPC functionality and directly sets values from the module's webapi.xml file.

When an RPC request is parsed by the server, the server uses the data found in the WSDL file to determine if the request is valid, checking the request's method, parameters, and types. If the request is valid, the parsed request object is passed to Magento for further processing. One very important point: the "SoapServer" does not interact with Magento in any way; all information about the module's methods and parameters comes from the WSDL file. At this point, the sent request still consists of nested arrays, and no objects are created during the SoapServer parsing phase. To create the required objects, Magento handles the input itself.

To extract parameter names and data types, Magento retrieves the prototype from the request's method (see the code above). For basic data types such as strings, arrays, booleans, etc., the system maps the input to the corresponding type. However, for object types, the resolution is more complicated.

If the parameter's data type is a class instance, Magento will attempt to create an instance using the provided input. Remember, the input at this point is just a dictionary, with keys as property names and values as property values.

First, Magento creates a new instance of the required class. Then, it tries to populate it using the following method:

  1. Get the property name (from the dictionary key in the input).
  2. Look for a public method named "Set[Name]", where [Name] is the property name.
  3. If such a method exists, execute it using the property value as an argument.
  4. If no such method exists, ignore the property and continue to the next one.

Magento processes each property the user is trying to set in this manner. Once all properties have been checked, Magento considers the instance fully set and moves to the next parameter. After all parameters have been processed, Magento finally executes the API method.

In summary, Magento allows you to create an object, set its public properties, and then execute any method starting with "Set" through its RPC. And it is this behavior that leads to the vulnerability.

Research found that some API calls allow setting specific information in a shopping cart, such as shipping addresses, products, or even payment methods.

When Magento sets our information in the cart instance, it uses the instance's "save" method to store the newly added data in the database.

Let's take a look at how the "save" method works!

root@kitploit:~
/**
* Save object data
*/
public function save(\Magento\Framework\Model\AbstractModel $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 ensures our object is valid, then serializes all parts that should be serialized and stores them in the database, and finally unserializes the parts that were serialized earlier.

Seems simple, right? Actually, not so. Let's continue to see how Magento determines which parts should be serialized.

root@kitploit:~
/**
* Serialize serializable fields of the object
*/
protected function _serializeFields(\Magento\Framework\Model\AbstractModel $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 we can see, only those fields present in the hardcoded dictionary "_serializableFields" can be serialized. Most importantly, this method continues to serialize only after ensuring the field's value is an array or an object.

Now, let's see how Magento determines which parts should be unserialized.

root@kitploit:~
/**
* Unserialize serializeable object fields
*/
public function unserializeFields(\Magento\Framework\Model\AbstractModel $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, it looks very similar. The only difference is that this time Magento needs to ensure the field's value is not an array or an object. Because of these two checks, we should be able to perform an object injection attack by simply setting a crafted string in a serializable field. When we do so, the system will not serialize this field before storing the object in the database because it is not an object or array. However, when the system later tries to unserialize it, after the database query has been executed, it will be unserialized because it is not an object or array.

But it is this almost invisible condition that creates the vulnerability. The remaining question is which fields are considered "serializable" and how we can set them.

Of course, the first question is simple: I just need to search for classes that contain the "_serializableFields" property. Soon, an API method was found in the "Payment" class, but not as a parameter, so it is not possible to create or control its instance properties. Most importantly, its serializable field "additional_information" can only be set as an array, using the "Set[PROPERTY_NAME]" technique as an additional security measure, so not only can we not create it, but even if we could, we couldn't set it as a string.

But interestingly, it can be set in a different, "slick" way. When Magento sets the properties of a parameter instance, it does not actually set the properties; rather, it stores them in a dictionary named "_data". This dictionary is used when an instance's property is accessed. For us, this means our serializable field – "additional_information" – is actually stored in an internal dictionary rather than as a normal property.

So, if we can fully control the "_data" dictionary, we can easily bypass the array restriction on the "additional_information" field, because we can set it manually instead of calling "Set[PROPERTY_NAME]".

But how do we control this sensitive dictionary?

Before saving our "Payment" instance, one of the things Magento does is edit its properties. Magento treats our API input as payment information that needs to be stored in the "Payment" instance, as follows:

root@kitploit:~
/**
* Adds a specified payment method to a specified shopping cart.
*/
public function set($cartId, \Magento\Quote\Api\Data\PaymentInterface $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 we can see, the "Payment" data is obtained by calling "$method->getData()" from the "$method" parameter, which returns the "_data" attribute. Remember, since "$method" is a parameter of the API method, we can control it.

When Magento calls "getData()" on our "$method" parameter, the parameter's "_data" attribute is returned, containing all the payment information we injected. Then, it calls "importData()" with the "_data" attribute as input, replacing the "Payment" instance's "_data" attribute with our "_data" attribute. At this point, we can now replace the sensitive "_data" attribute of the "Payment" instance with our controllable "_data" attribute, meaning we can now set the "additional_information" field.

For unserialize() to work, we need the field to be settable as a string, but the "Set[PROPERTY_NAME]" method only allows arrays. The solution lies in the two lines of code placed before calling "importData()". Magento allows developers to add their own payment methods, providing their own data and information. To achieve this, Magento uses the "additional_data" field. This field is a dictionary containing additional data for the payment method and is fully user-controllable. To make the customized content part of the original data, Magento merges the "additional_data" dictionary with the original "data" dictionary. This effectively allows the "additional_data" dictionary to override any values in the "data" dictionary, essentially allowing complete overwriting. This means that after the two dictionaries are merged, the user-controllable "additional_data" dictionary now becomes the parameter's "_data" dictionary, and because of "importData()", it also becomes the sensitive "_data" attribute of the "Payment" instance. In other words, we now have full control over the serializable field "additional_information" and can perform object injection.

Since we can unserialize any string we want, it's time to perform an object injection attack.

First, we need an object with a "__wakeup()" or "__destruct()" method that will be automatically called when the object is unserialized or destroyed. This is because even though we can control the object's properties, we cannot call its methods. That's why we must rely on PHP's magical methods, which are automatically called when a certain event occurs.

The first object we will use is an instance of the "Credis_Client" class, which contains the following methods:

root@kitploit:~
/*
* 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()

We can see that this class has a simple "__destruct" method (automatically called by PHP when the object is destroyed) that calls the "close()" method. Interestingly, the "close()" method, if it finds an active connection to a Redis server, will call "close()" on the "redis" property to close it.

Since unserialize() allows us to control all object properties, we can also control the "redis" property. We can set any object we want in this property (not just Redis) and call any "close()" method on any class in the system. This greatly expands our attack surface. There are several "close()" methods in Magento, and since they are typically used to close streams, file handles, and store object data, we should be able to find some interesting calls.

As expected, we found the following "close()" method in the "Transaction" class:

root@kitploit:~
/**
* Close this transaction
*/
public function close($shouldSave = true)
{
...
if ($shouldSave) {
    $this->save();
}
...
}
/**
* Save object data
*/
public function save()
{
$this->_getResource()->save($this);
return $this;
}
// Magento\Sales\Model\Order\Payment\Transaction::__destruct(), close()

It looks simple: the "close()" method calls "save()", which in turn calls the "save()" method on the "_resource" property. Following the same logic, since we control the "_resource" property, we can also control its class, so we can call the "save()" method on any class we want.

A big step forward. As we suspected, the "save()" method is typically used to save various data to storage media (e.g., filesystem, database, etc.). Now we just need to find a "save()" method that uses the filesystem as its storage medium.

Soon, I found one:

root@kitploit:~
/**
* Try to save configuration cache to file
*/
public function save()
{
...
// save stats
file_put_contents($this->getStatFileName(), $this->getComponents());
...
}
// Magento\Framework\Simplexml\Config\Cache\File::save()

This method actually saves the data from the "components" field into a file. Since the file path is obtained from the "stat_file_name" field, and we control both parameters, we effectively control the file path and content. This creates an arbitrary file write vulnerability.

Now we just need to find a valid writable path accessible by the web server. In all Magento installations, there is a "/pub" directory used to store images or files uploaded by administrators. This is a usable path.

Finally, we simply write a PHP webshell file to the server, allowing us to execute arbitrary PHP code on the Magento server without authentication.

0x02 Exploitation

Test Environment Setup

  1. Download the vulnerable package (version 2.0.0 is used here) Download link: https://github.com/magento/magento2/archive/2.0.0.zip
  2. Install Magento Installation guide: https://github.com/magento/magento2/tree/2.0.0

Note: Some issues may be encountered; refer to:

  • http://magento2king.com/magento2-insta-be-downloaded/
  • https://github.com/magento/magento2/issues/2419

Exploitation

The exploit published on exploit-db can be downloaded at: https://www.exploit-db.com/exploits/39838/

Exploitation steps:

  1. Find a vulnerable Magento website Check Magento version online: http://magentoversion.com/

  2. Add a product to the shopping cart Input image description here

  3. Enter the shopping cart and click "Checkout" Input image description here

  4. Fill in the shipping address and view the POST request to /rest/default/V1/guest-carts/[guestCartId]/shipping-information and obtain [guestCartID] Input image description here Input image description here

  5. Save the exploit above as magento_exp.php and execute: php magento_exp.php [Magento_URL] [guestCartID] ([webshell_write_path]) Input image description here

Batch Scanning

After researching the exploit above, it was found that exploitation requires the following conditions:

  1. The target site's Magento version must be less than 2.0.6 and REST API must be enabled.
  2. The following JavaScript must exist on the target site's homepage Input image description here

Therefore, a simple batch verification script was written to assist with the exploit above:

root@kitploit:~
#!/usr/bin/env python
import urllib
import sys
import socket
timeout = 5
socket.setdefaulttimeout(timeout)

input = sys.argv[1]  # File containing URLs of Magento sites
output = sys.argv[2] # Output file for results, e.g., output.txt

def logFile(str):
    f = open(output,'a')
    f.write(str+"\n")
    f.close()

def checkVul(url):
    try:
	    html = urllib.urlopen(url).read()
	    if "guest-carts" in html:
		    print url,"is vulnerable!"
		    logFile(url)
	    else:
		    print url,"is not vulnerable!"
    except Exception:
	    pass

if __name__ == '__main__':
    inp = open(input,'r')
    for i in inp:
	    url=i.strip()
	    #print url
	    checkVul(url)
    print "All Done!"

Execution result: Input image description here

0x03 Defense

Upgrade Magento to the latest version (2.0.6). Download link: https://www.magentocommerce.com/download

References

  • http://netanelrub.in/2016/05/17/magento-unauthenticated-remote-code-execution/

  • https://www.exploit-db.com/exploits/39838/

Download Tool