Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
Magento-CVE-2016-4010 — Ejecución remota de código no autorizada en Magento (CVE-2016-4010) | Kitploit
Herramientas/GitHubGitHub/brianwrf/magento-cve-2016-4010
Análisis de VulnerabilidadesAnálisis de CódigoExplotaciónExplotación de Aplicaciones WebPruebas de PenetraciónAprendizaje y EducaciónLabs y Práctica
GitHubbrianwrf/magento-cve-2016-4010

Magento-CVE-2016-4010

Ejecución remota de código no autorizada en Magento (CVE-2016-4010)

Ver Repositorio
632hace 10 añosAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Análisis y explotación de la vulnerabilidad de ejecución remota de código no autenticada en Magento (CVE-2016-4010)


0x00 Prefacio

El 17 de mayo, el investigador de seguridad Netanel Rubin publicó una vulnerabilidad de ejecución remota de código no autenticada en Magento (CVE-2016-4010). Esta vulnerabilidad engloba en realidad varios fallos menores y permite a un atacante ejecutar código PHP de forma no autenticada en un servidor Magento vulnerable. Magento es una plataforma de comercio electrónico muy popular que fue adquirida por eBay en 2011. Algunas empresas conocidas, como Samsung, Nikon, Lenovo, así como numerosas pequeñas tiendas en línea, la utilizan. Se dice que Magento es usado por 250 000 tiendas en línea, moviendo alrededor de 60 000 millones de dólares al año.

0x01 Análisis

Condiciones para explotar la vulnerabilidad:

  • Magento tiene habilitados los RPC (REST o SOAP), y la mayoría los tiene habilitados por defecto.
  • Versiones CE y EE de Magento < 2.0.6

La API web de Magento permite dos tipos distintos de RPC: REST RPC y SOAP API. Ambos ofrecen la misma funcionalidad; la única diferencia es que el primero usa JSON y peticiones HTTP para transferir la entrada, mientras que el segundo usa XML.

Para exponer únicamente las API de algunos módulos, Magento ofrece a los desarrolladores un método práctico: declarar en el archivo "webapi.xml" solamente las API de los módulos a los que desean poder acceder. El archivo webapi.xml contiene todas las clases y métodos de las Web API que necesitan ser públicas, y cada método especifica también el permiso concreto que requiere. Estos permisos incluyen:

  • anonymous — permite el acceso a cualquier persona
  • self — permite únicamente a usuarios registrados y a administradores con permisos específicos; por ejemplo, el permiso "Magento_Backend::admin" solo permite acceder a los administradores que pueden editar la configuración del servidor.

Por supuesto, esta forma de permitir a los desarrolladores usar el archivo webapi.xml para comunicarse entre el frontend y el backend del sistema (Web API) abre en realidad una puerta trasera directa al núcleo de los módulos.

Además, incluso teniendo el permiso "anonymous", todavía necesitamos una forma de pasar valores dinámicamente. Aquí nos referimos a los distintos objetos que se pueden usar en el sistema; por ejemplo, la función de API "CustomerRepositoryInterface::save()" nos permite usar un objeto "CustomerInterface" en la variable "$customer". El prototipo del código es el siguiente:

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

Entonces, ¿cómo podemos crear objetos usando la interfaz RPC? De hecho, la respuesta a esta pregunta está en cómo Magento configura el servidor SOAP.

Magento usa un servidor SOAP que incorpora por defecto el "SoapServer" de PHP. Para configurarse correctamente, "SoapServer" necesita un archivo WSDL donde se definen todos los métodos, parámetros y los tipos personalizados usados en las peticiones RPC reales. Magento genera distintos archivos WSDL para cada módulo con soporte de funcionalidad XMLRPC, y asigna directamente los valores provenientes del archivo webapi.xml del módulo.

Cuando el servidor procesa una petición RPC, usa los datos del archivo WSDL para determinar si la petición es válida, comprobando el método, los parámetros y los tipos de la petición. Si la petición es válida, pasa el objeto de petición analizado a Magento para su posterior procesamiento. Un punto muy importante es que "SoapServer" no interactúa con Magento de ninguna manera; toda la información sobre los métodos y parámetros de los módulos proviene del archivo WSDL. En ese momento, la petición enviada sigue estando compuesta por matrices anidadas; durante la fase de análisis del SoapServer no se crea ningún objeto. Para crear los objetos necesarios, Magento continúa procesando la entrada por su cuenta.

Para extraer los nombres de los parámetros y los tipos de datos, Magento obtiene el prototipo del método de la petición (ver el código anterior). Para algunos tipos de datos básicos, como cadenas, matrices, booleanos, etc., el sistema asigna la entrada al tipo correspondiente. Pero para los tipos de objeto, la solución es más problemática.

Si el tipo de datos de un parámetro es una instancia de una clase, Magento intentará crear una instancia usando la entrada proporcionada. Recuerde que, en este momento, la entrada es solo un diccionario cuyas claves son nombres de atributos y cuyos valores son valores de atributos.

Primero, Magento creará una nueva instancia de la clase requerida. Luego, intentará rellenarla usando el siguiente método:

  1. Obtener el nombre del atributo (de las claves del diccionario de entrada).
  2. Buscar un método público llamado "Set[Name]", donde [Name] es el nombre del atributo.
  3. Si existe tal método, ejecutarlo usando el valor del atributo como argumento.
  4. Si no existe tal método, ignorar el atributo y continuar con el siguiente.

Magento procesará de esta manera cada atributo que el usuario esté intentando establecer. Cuando se hayan revisado todos los atributos, Magento considerará que la instancia está configurada y procesará el siguiente parámetro. Cuando todos los parámetros hayan sido procesados de esta forma, Magento ejecutará finalmente el método de la API.

En resumen, Magento le permite crear un objeto, establecer sus propiedades públicas y, finalmente, ejecutar a través de su RPC cualquier método que comience con "Set". Y es precisamente este comportamiento el que dio lugar a la vulnerabilidad en Magento.

La investigación descubrió que algunas llamadas a la API permiten establecer información concreta en el carrito de compra, información que puede ser nuestra dirección de envío, productos e incluso nuestra forma de pago.

Cuando Magento establece nuestra información en la instancia del carrito, usa el método "save" de la instancia para almacenar los datos recién agregados en la base de datos.

A continuación, veamos cómo funciona el método "save":

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 se asegura de que nuestro objeto sea válido, luego serializa todas las partes que deben serializarse y las almacena en la base de datos, y finalmente deserializa las partes previamente serializadas.

Parece sencillo, ¿verdad? Pues no lo es. Sigamos viendo cómo determina Magento qué partes deben serializarse.

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()

Como podemos ver, solo las partes que aparecen en el diccionario hardcodeado "_serializableFields" pueden ser serializadas. Lo más importante es que este método solo continúa con la serialización después de asegurarse de que el valor del campo sea una matriz o un objeto.

Ahora veamos cómo determina Magento qué partes deben deserializarse.

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 ()

Bien, parece muy similar. La única diferencia es que, esta vez, Magento necesita asegurarse de que el valor del campo no sea una matriz ni un objeto. Debido a estas dos comprobaciones, deberíamos poder llevar a cabo un ataque de inyección de objetos, simplemente estableciendo una cadena con una regla determinada en un campo serializable. Si lo hacemos así, el sistema no serializará este campo antes de almacenar el objeto en la base de datos, porque no es un objeto ni una matriz. Sin embargo, cuando el sistema intente deserializarlo, después de que se ejecute la consulta a la base de datos, será deserializado, porque no es un objeto ni una matriz.

Pero es precisamente esta condición, tan pequeña que casi pasa desapercibida, la que causa la vulnerabilidad. La cuestión restante es considerar qué campos se consideran "serializables" y cómo podemos establecerlos.

Por supuesto, la primera pregunta es simple: solo necesito buscar qué clase contiene la propiedad "_serializableFields". Rápidamente encontré un método de API en la clase "Payment", pero no como parámetro, por lo que no se puede crear ni controlar sus propiedades de instancia. Lo más importante es que su campo serializable "additional_information" solo puede establecerse como una matriz, y se usa la técnica "Set[PROPERTY_NAME]" como medida de seguridad adicional, de modo que no solo no se puede crear, sino que aunque pudiéramos, no podríamos establecerlo como una cadena.

Pero es interesante que se pueda establecer de otra manera "peculiar". Cuando Magento establece las propiedades de la instancia del parámetro, en realidad no establece las propiedades tal cual, sino que las guarda en un diccionario llamado "_data". Cuando se utiliza una propiedad de la instancia, se usa este diccionario. Para nosotros, esto significa que nuestro campo serializable — "additional_information" — se guarda en realidad en un diccionario interno en lugar de en una propiedad normal.

Así que, si pudiéramos controlar completamente el diccionario "_data", podríamos eludir fácilmente la restricción de matriz del campo "additional_information", porque podemos establecerlo manualmente en lugar de llamar a "Set[PROPERTY_NAME]".

Pero, ¿cómo podemos controlar este diccionario sensible?

Antes de guardar nuestra instancia de "Payment", una de las cosas que Magento hace es editar sus propiedades. Magento trata nuestra entrada de la API como la información de pago que debe almacenarse en la instancia de "Payment", como se muestra a continuación:

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()

Como podemos ver, los datos de "Payment" se obtienen llamando a "$method->getData()", que devuelve la propiedad "_data" del parámetro "$method". Recuerde que, como "$method" es un parámetro del método de la API, podemos controlarlo.

Cuando Magento llama a "getData()" en nuestro parámetro "$method", se devolverá la propiedad "_data" del parámetro, que contiene toda la información de pago que insertamos. Después, llama a "importData()" usando la propiedad "_data" como entrada, reemplazando la propiedad "_data" de la instancia de "Payment" con nuestra propiedad "_data". En este punto, ahora podemos usar la propiedad "_data" que controlamos para sustituir la sensible propiedad "_data" de la instancia de "Payment", lo que significa que ahora podemos establecer el campo "additional_information".

Para que unserialize() funcione, necesitamos que el campo pueda establecerse como una cadena, pero el método "Set[PROPERTY_NAME]" solo permite matrices. La solución está en las dos líneas de código que se ejecutan antes de llamar a "importData()". Magento permite a los desarrolladores añadir sus propios métodos de pago, proporcionando sus propios datos e información. Para lograrlo, Magento usa el campo "additional_data". Este campo es un diccionario, completamente controlable por el usuario, que contiene más datos del método de pago. Para que el contenido personalizado forme parte de los datos originales, Magento fusiona el diccionario "additional_data" con el diccionario "data" original, lo que en la práctica permite que el diccionario "additional_data" sobrescriba todos los valores del diccionario "data", básicamente permitiendo una sobrescritura completa. Esto significa que, después de fusionar los dos diccionarios, el diccionario "additional_data" controlado por el usuario se convierte ahora en el diccionario "_data" del parámetro y, debido a "importData()", también se convierte en la sensible propiedad "_data" de la instancia de "Payment". En otras palabras, ahora controlamos completamente el campo serializable "additional_information" y podemos llevar a cabo un ataque de inyección de objetos.

Ya que podemos deserializar cualquier cadena que queramos, es hora de realizar el ataque de inyección de objetos.

Primero, necesitamos un objeto con un método "__wakeup()" o "__destruct()" para que se llame automáticamente cuando el objeto sea deserializado o destruido. Esto se debe a que, aunque podemos controlar las propiedades del objeto, no podemos llamar a sus métodos. Por eso debemos depender de los métodos mágicos de PHP, que se llaman automáticamente cuando ocurre cierto evento.

El primer objeto que usaremos es una instancia de la clase "Credis_Client", que contiene los siguientes métodos:

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()

Podemos ver que esta clase tiene un simple método "__destruct" (que PHP llamará automáticamente cuando el objeto sea destruido) para llamar a "close()". Lo interesante es que el método "close()", si detecta una conexión activa a un servidor Redis, llamará a "close()" en la propiedad "redis" para cerrarla.

Dado que "unserialize()" nos permite controlar todas las propiedades del objeto, también podemos controlar la propiedad "redis". Podemos establecer en la propiedad cualquier objeto que queramos (no solo Redis) y llamar a cualquier método "close()" de cualquier clase del sistema. Esto amplía enormemente nuestra superficie de ataque. En Magento hay varios métodos "close()", y como estos métodos suelen usarse para cerrar flujos, cerrar descriptores de archivo y almacenar datos de objetos, deberíamos poder encontrar algunas llamadas interesantes.

Como esperábamos, encontramos el siguiente método "close()" en la clase "Transaction":

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()

Parece simple: el método "close()" llama a "save()", que a su vez llama al método "save()" de la propiedad "_resource". Siguiendo el mismo razonamiento, como controlamos la propiedad "_resource", también controlamos su clase, por lo que podemos llamar al método "save()" de cualquier clase que queramos.

Otro gran paso adelante. Como suponíamos, el método "save()" se usa normalmente para guardar diversos tipos de datos en distintos medios de almacenamiento (como sistema de archivos, base de datos, etc.). Ahora lo que necesitamos hacer es encontrar un método "save()" que use el sistema de archivos como medio de almacenamiento.

Pronto encontré uno:

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()

Este método guarda en un archivo los datos del campo "components". Dado que la ruta del archivo se obtiene del campo "stat_file_name", y como controlamos ambos parámetros, en realidad controlamos la ruta y el contenido del archivo, lo que produce una vulnerabilidad de escritura arbitraria de archivos.

Ahora solo necesitamos encontrar una ruta válida, escribible y accesible por el servidor web para escribir el archivo. En todos los directorios de instalación de Magento hay un directorio "/pub", que se utiliza para almacenar imágenes o archivos subidos por los administradores; es una ruta explotable.

Finalmente, solo tenemos que escribir un simple archivo webshell en PHP en el servidor, y podremos ejecutar código PHP arbitrario de forma no autenticada en el servidor Magento.

0x02 Explotación

Configuración del entorno de pruebas

  1. Descargar el paquete de instalación vulnerable (aquí se usa la versión 2.0.0). Dirección de descarga: https://github.com/magento/magento2/archive/2.0.0.zip
  2. Instalar Magento Pasos de instalación: https://github.com/magento/magento2/tree/2.0.0

Nota: aquí pueden surgir algunos problemas; ver:

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

Explotación de la vulnerabilidad

Dirección de descarga del exploit público en exploit-db: https://www.exploit-db.com/exploits/39838/

Método de explotación:

  1. Encontrar un sitio web Magento vulnerable. Comprobación en línea de la versión de Magento: http://magentoversion.com/

  2. Añadir un producto al carrito de compra. Ingrese aquí la descripción de la imagen

  3. Entrar en el carrito y hacer clic en "Pagar". Ingrese aquí la descripción de la imagen

  4. Rellenar la dirección de envío y revisar la petición POST /rest/default/V1/guest-carts/[guestCartId]/shipping-information y obtener el [guestCartID]. Ingrese aquí la descripción de la imagen Ingrese aquí la descripción de la imagen

  5. Guardar el exploit anterior como magento_exp.php y ejecutar: php magento_exp.php [Magento_URL] [guestCartID] ([ruta de escritura del webshell]) Ingrese aquí la descripción de la imagen

Detección masiva

Tras investigar el exploit anterior, se descubrió que su explotación requiere cumplir las siguientes condiciones:

  1. La versión de Magento del sitio objetivo debe ser inferior a 2.0.6 y tener la API REST habilitada.
  2. La página principal del sitio objetivo debe contener el siguiente fragmento de JS. Ingrese aquí la descripción de la imagen

Por lo tanto, escribí un sencillo script de verificación masiva para complementar el exploit anterior:

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

input = sys.argv[1]  #archivo que contiene las URLs de sitios Magento
output = sys.argv[2] #archivo para guardar los resultados; puede ser: 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!"

Resultado de la ejecución: Ingrese aquí la descripción de la imagen

0x03 Defensa

Actualizar Magento a la última versión (2.0.6). Dirección de descarga: https://www.magentocommerce.com/download

Referencias

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

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

Descargar herramienta