Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
Ferramentas/GitHubGitHub/brianwrf/magento-cve-2016-4010
Análise de VulnerabilidadesAnálise de CódigoExploraçãoExploração de Aplicações WebTestes de PenetraçãoAprendizado e EducaçãoLabs e Prática
GitHubbrianwrf/magento-cve-2016-4010

Magento-CVE-2016-4010

Magento - Execução Remota de Código Não Autorizada (CVE-2016-4010)

Ver Repositório
632há 10 anosAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Análise e Exploração da Vulnerabilidade de Execução Remota de Código Não Autenticada no Magento (CVE-2016-4010)


0x00 Prefácio

Em 17 de maio, o pesquisador de segurança Netanel Rubin divulgou uma vulnerabilidade de execução remota de código não autenticada no Magento (CVE-2016-4010). Essa vulnerabilidade na verdade engloba várias pequenas falhas e permite que um atacante execute código PHP não autenticado em servidores Magento vulneráveis. Magento é uma plataforma de e-commerce muito popular, adquirida pela eBay em 2011. Empresas conhecidas, como Samsung, Nikon, Lenovo e muitas outras pequenas lojas virtuais, a utilizam. Segundo informações, o Magento é usado por 250.000 lojas online, movimentando anualmente um montante de 60 bilhões de dólares.

0x01 Análise

Condições de exploração da vulnerabilidade:

  • O Magento tem RPCs ativados (REST ou SOAP), e a maioria vem ativada por padrão.
  • Versões CE & EE do Magento < 2.0.6

A API web do Magento permite dois tipos diferentes de RPCs: REST RPC e SOAP API. Ambos oferecem as mesmas funcionalidades, a única diferença é que o primeiro usa JSON e requisições HTTP para transmitir entradas, enquanto o segundo usa XML.

Para expor apenas as APIs de alguns módulos, o Magento oferece aos desenvolvedores um método conveniente: declarar no arquivo "webapi.xml" apenas as APIs dos módulos que desejam tornar acessíveis. O arquivo webapi.xml contém todas as classes e métodos das Web APIs que precisam ser expostos, e cada método também especifica a permissão específica necessária. Essas permissões incluem:

  • anonymous – permite acesso de qualquer pessoa ao método
  • self – permite acesso apenas a usuários registrados e permissões específicas de administrador, por exemplo: a permissão "Magento_Backend::admin" permite acesso apenas a administradores que podem editar configurações do servidor

Claro, essa forma de permitir que desenvolvedores usem o arquivo webapi.xml para comunicação entre o frontend e o backend (Web API) do sistema também abre uma porta direta para o núcleo do módulo.

Além disso, mesmo que tenhamos permissão "anonymous", ainda precisamos de uma maneira de passar valores dinamicamente. Refere-se aqui aos diferentes objetos que podem ser usados no sistema. Por exemplo: a função da API CustomerRepositoryInterface::save() permite usar um objeto CustomerInterface na variável $customer. O protótipo do código é o seguinte:

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

Então, como usar a interface RPC para criar objetos? Na verdade, a resposta está em como o Magento configura o servidor SOAP.

O Magento usa o servidor SOAP padrão do PHP, o "SoapServer". Para configurá-lo corretamente, o "SoapServer" precisa de um arquivo WSDL que defina todos os métodos, parâmetros e tipos personalizados usados nas requisições RPC reais. O Magento gera diferentes arquivos WSDL para cada módulo que suporta funcionalidades XMLRPC, e define diretamente os valores do arquivo webapi.xml do módulo.

Quando uma requisição RPC é analisada pelo servidor, o servidor usa os dados encontrados no arquivo WSDL para determinar se a requisição é válida, verificando o método, parâmetros e tipos. Se for válida, o objeto de requisição analisado é passado para o Magento para processamento adicional. Um ponto muito importante é que o "SoapServer" não interage com o Magento de forma alguma; todas as informações sobre os métodos e parâmetros do módulo vêm do arquivo WSDL. Neste ponto, a requisição enviada ainda é composta por arrays aninhados; nenhum objeto é criado durante a fase de análise do SoapServer. Para criar os objetos necessários, o Magento continua processando a entrada por conta própria.

Para extrair os nomes dos parâmetros e tipos de dados, o Magento obtém o protótipo do método da requisição (veja o código anterior). Para tipos de dados básicos, como string, array, booleano, etc., o sistema mapeia a entrada para o tipo correspondente. Mas para tipos de objeto, a solução é mais complicada.

Se o tipo de dado de um parâmetro for uma instância de uma classe, o Magento tentará construir a instância usando a entrada fornecida. Lembre-se: a entrada neste momento é apenas um dicionário, cujas chaves são nomes de propriedades e os valores são valores de propriedades.

Primeiro, o Magento criará uma nova instância da classe necessária. Em seguida, tentará preenchê-la usando o seguinte método:

  1. Obter o nome da propriedade (da chave do dicionário de entrada)
  2. Procurar por um método público chamado "Set[Nome]", onde [Nome] é o nome da propriedade
  3. Se existir tal método, executá-lo usando o valor da propriedade como argumento
  4. Se não existir tal método, ignorar a propriedade e continuar para a próxima

O Magento processará dessa forma cada propriedade que o usuário está tentando definir. Quando todas as propriedades forem verificadas, o Magento considera a instância completamente configurada e processa o próximo parâmetro. Quando todos os parâmetros forem tratados assim, o Magento executará finalmente o método da API.

Em resumo, o Magento permite que você crie um objeto, defina suas propriedades públicas e, finalmente, execute qualquer método que comece com "Set" através de sua RPC. E é exatamente esse comportamento que leva à vulnerabilidade no Magento.

A pesquisa descobriu que algumas chamadas de API permitem definir informações específicas no carrinho de compras, como nosso endereço de entrega, produtos e até mesmo nossa forma de pagamento.

Quando o Magento define nossas informações na instância do carrinho, ele usa o método "save" da instância para armazenar os novos dados no banco de dados.

Vamos ver como o método "save" funciona!

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

O Magento garante que nosso objeto seja válido, serializa todas as partes que devem ser serializadas, armazena no banco de dados e, finalmente, desserializa as partes serializadas anteriormente.

Parece simples, certo? Na verdade não. Vamos continuar vendo como o Magento determina quais partes devem ser serializadas.

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 vemos, apenas os campos que aparecem no dicionário codificado "_serializableFields" podem ser serializados. O mais importante é que este método só prossegue com a serialização se o valor do campo for um array ou um objeto.

Agora, vejamos como o Magento determina quais partes devem ser desserializadas.

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

Bem, parece muito similar. A única diferença é que desta vez o Magento precisa garantir que o valor do campo não seja um array ou um objeto. Por causa dessas duas verificações, devemos conseguir realizar um ataque de injeção de objetos, simplesmente definindo uma string com uma regra específica em um campo serializável. Quando fazemos isso, o sistema não serializará esse campo antes de armazenar o objeto no banco de dados, porque ele não é um objeto ou array. No entanto, quando o sistema tentar desserializá-lo, após a execução da consulta ao banco de dados, ele será desserializado, pois não é um objeto ou array.

Mas é justamente essa condição tão pequena, quase imperceptível, que causa a vulnerabilidade. A questão restante é considerar quais campos são considerados "serializáveis" e como podemos defini-los.

Claro, a primeira pergunta é simples: basta procurar qual classe contém a propriedade "_serializableFields". Rapidamente, encontramos um método de API na classe "Payment", mas não como parâmetro, portanto não podemos criar ou controlar suas propriedades de instância. O mais importante é que seu campo serializável "additional_information" só pode ser definido como um array, usando a técnica "Set[PROPERTY_NAME]" como uma medida de segurança adicional, então não apenas não podemos criá-lo, mas mesmo que pudéssemos, não poderíamos defini-lo como uma string.

Mas é interessante que podemos defini-lo de uma maneira "estranha". Quando o Magento define as propriedades da instância do parâmetro, na verdade não está definindo propriedades reais, mas sim armazenando-as em um dicionário chamado "_data". Quando uma propriedade da instância é usada, esse dicionário é consultado. Para nós, isso significa que nosso campo serializável "additional_information" está, na verdade, armazenado em um dicionário interno, em vez de uma propriedade normal.

Portanto, se pudermos controlar completamente o dicionário "_data", podemos facilmente contornar a restrição de array do campo "additional_information", pois podemos defini-lo manualmente em vez de chamar "Set[PROPERTY_NAME]".

Mas como controlamos esse dicionário sensível?

Antes de salvar nossa instância "Payment", o Magento faz uma coisa: edita suas propriedades. O Magento trata nossa entrada da API como informações de pagamento que devem ser armazenadas na instância "Payment", da seguinte forma:

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 vemos, os dados "Payment" são obtidos chamando $method->getData() no parâmetro $method, que retorna a propriedade "_data". Lembre-se: como $method é um parâmetro do método da API, podemos controlá-lo.

Quando o Magento chama getData() em nosso parâmetro $method, a propriedade "_data" do parâmetro é retornada, contendo todas as informações de pagamento que inserimos. Em seguida, ele chama importData() com o "_data" como entrada, substituindo a propriedade "_data" da instância "Payment" pelo nosso "_data". Até este ponto, podemos usar a propriedade "_data" que controlamos para substituir a propriedade "_data" sensível da instância "Payment", o que significa que agora podemos definir o campo "additional_information".

Para que unserialize() funcione, precisamos que o campo possa ser definido como uma string, mas o método "Set[PROPERTY_NAME]" só permite arrays. A solução está nas duas linhas de código antes da chamada a importData(). O Magento permite que desenvolvedores adicionem seus próprios métodos de pagamento, fornecendo seus próprios dados e informações. Para isso, o Magento usa o campo "additional_data". E esse campo é um dicionário que contém mais dados do método de pagamento e é totalmente controlável pelo usuário. Para que o conteúdo personalizado se torne parte dos dados originais, o Magento mescla o dicionário "additional_data" com o dicionário original "data", permitindo efetivamente que o dicionário "additional_data" substitua todos os valores do dicionário "data", ou seja, pode sobrescrever completamente. Isso significa que, após a mesclagem dos dois dicionários, o dicionário "additional_data" controlado pelo usuário agora se torna o dicionário "_data" do parâmetro e, devido a importData(), também se torna a propriedade "_data" da instância "Payment". Em outras palavras, agora temos controle total sobre o campo serializável "additional_information" e podemos realizar um ataque de injeção de objetos.

Agora que podemos desserializar qualquer string que quisermos, é hora de realizar o ataque de injeção de objetos.

Primeiro, precisamos de um objeto com um método __wakeup() ou __destruct(), para que seja chamado automaticamente quando o objeto for desserializado ou destruído. Isso porque, embora possamos controlar as propriedades do objeto, não podemos chamar seus métodos. Por isso, dependemos dos métodos mágicos do PHP, que são chamados automaticamente quando determinados eventos ocorrem.

O primeiro objeto que usaremos é uma instância da classe Credis_Client, que contém o seguinte método:

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 essa classe tem um simples método __destruct (chamado automaticamente pelo PHP quando o objeto é destruído) que chama o método close(). O interessante é que o método close(), se detectar uma conexão ativa com o servidor Redis, chama o método close() da propriedade "redis" para fechá-la.

Como unserialize() nos permite controlar todas as propriedades do objeto, também podemos controlar a propriedade "redis". Podemos definir qualquer objeto que quisermos na propriedade (não apenas Redis) e chamar qualquer método close() em qualquer classe do sistema. Isso amplia muito nossa superfície de ataque. Existem alguns métodos close() no Magento e, como geralmente são usados para encerrar fluxos, fechar manipuladores de arquivos e armazenar dados de objetos, devemos encontrar algumas chamadas interessantes.

Como esperado, encontramos o seguinte método close() na classe "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 simples: o método close() chama save(), que por sua vez chama o método save() da propriedade "_resource". Com o mesmo raciocínio, como controlamos a propriedade "_resource", também podemos controlar sua classe, portanto podemos chamar o método save() de qualquer classe que desejarmos.

Mais um grande passo. Como suspeitávamos, o método save() geralmente é usado para salvar vários tipos de dados em meios de armazenamento (como sistema de arquivos, banco de dados, etc.). Agora precisamos encontrar um método save() que use o sistema de arquivos como meio de armazenamento.

Rapidamente encontrei um:

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

Esse método basicamente salva os dados do campo "components" em um arquivo. Como o caminho do arquivo é obtido do campo "stat_file_name" e controlamos ambos os parâmetros, na verdade controlamos o caminho e o conteúdo do arquivo, gerando uma vulnerabilidade de gravação arbitrária de arquivos.

Agora só precisamos encontrar um caminho válido, gravável e acessível pelo servidor web para escrever o arquivo. Em todos os diretórios de instalação do Magento, existe um diretório "/pub", usado para armazenar imagens ou arquivos enviados pelo administrador, sendo um caminho viável.

Finalmente, basta escrever um arquivo PHP webshell no servidor para executar código PHP arbitrário de forma não autenticada no servidor Magento.

0x02 Exploração

Configuração do ambiente de teste

  1. Baixar o pacote de instalação vulnerável (aqui usamos a versão 2.0.0) Link de download: https://github.com/magento/magento2/archive/2.0.0.zip
  2. Instalar o Magento Passos de instalação: https://github.com/magento/magento2/tree/2.0.0

Observação: alguns problemas podem ser encontrados; veja:

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

Exploração da vulnerabilidade

Link para download do exploit público no exploit-db: https://www.exploit-db.com/exploits/39838/

Método de exploração:

  1. Encontrar um site Magento vulnerável Verificação online da versão do Magento: http://magentoversion.com/

  2. Adicionar um produto ao carrinho de compras Descrição da imagem

  3. Entrar no carrinho e clicar em "Finalizar compra" Descrição da imagem

  4. Preencher o endereço de entrega e verificar a requisição POST /rest/default/V1/guest-carts/[guestCartId]/shipping-information e obter o [guestCartID] Descrição da imagem Descrição da imagem

  5. Salvar o exploit acima como magento_exp.php e executar: php magento_exp.php [Magento_URL] [guestCartID] ([caminho de gravação do webshell]) Descrição da imagem

Verificação em massa

Após estudar o exploit acima, descobrimos que a exploração precisa atender às seguintes condições:

  1. A versão do Magento do site alvo precisa ser inferior a 2.0.6 e a REST API deve estar ativada
  2. A página inicial do site alvo precisa conter o seguinte trecho de JavaScript Descrição da imagem

Portanto, escrevemos um script simples de verificação em massa para usar com o exploit acima:

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

input = sys.argv[1]  #Arquivo contendo URLs de sites Magento
output = sys.argv[2] #Arquivo para salvar resultados, pode 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 da execução: Descrição da imagem

0x03 Defesa

Atualizar o Magento para a versão mais recente (2.0.6). Link de download: https://www.magentocommerce.com/download

Referências

  • http://netanelrub.in/2016/05/17/magento-unauthenticated-remote-code-execution/
  • https://www.exploit-db.com/exploits/39838/
Baixar ferramenta