Neste artigo, vamos analisar um estudo de caso de uma CVE encontrada em um plugin do WordPress – wpDataTables, quando anterior à versão 2.1.66, que consiste em uma vulnerabilidade da categoria A8: Insecure Deserialization (desserialização insegura).
Estudo de Caso: plugin do WordPress
Recentemente foi identificada uma vulnerabilidade em um plugin do WordPress – wpDataTables, classificada como CWE-502: Deserialization of Untrusted Data. Esta vulnerabilidade pode ser classificada como A8: Insecure Deserialization.
Através desta vulnerabilidade, tecnicamente, é possível passar conteúdo desserializável por um parâmetro de um endpoint admin ajax do WordPress e realizar injeção de objetos PHP. Além disso, caso seja encontrado um ou múltiplos gadgets chains adequados é possível obter execução remota de comando no sistema. Ademais, a brecha de segurança também pode ser explorada pelo frontend do plugin.
A vulnerabilidade ocorre em uma função chamada serializedPhpRenderData do arquivo class.wpdatatables.php, a qual faz uma requisição HTTP a uma URL externa e desserializa o conteúdo através da função nativa unserialize do PHP.

Exploração
Primeiramente, para explorarmos a vulnerabilidade podemos simular um gadget chain adicionando no final do arquivo source/class.wpdatatables.php do plugin o seguinte código:
class Evil {
public function __wakeup() : void {
die(“Arbitrary deserialization”);
}
}
Este código representa uma classe chamada Evil e possui a função mágica __wakeup que será chamada logo após instanciar a classe através da desserialização insegura e que invocará a função die do PHP para fins de testes.

Após isso, é necessário armazenar o payload O:4:”Evil”:0:{}; em um arquivo acessível por URL pública (ex.: por webserver, pastebin e etc) para que seja posteriormente executado pelo plugin. Este é o payload que vai instanciar a classe Evil do gadget chain que adicionamos no código para fins de testes durante a desserialização insegura.

Em seguida, entramos na página wpDataTables > Create a Table > Create a data table linked to an existing data source.

Na entrada de seleção Input data source type selecionamos Serialized PHP array.

Em Input file path or URLcolocamos a URL contendo o payload e pressionamos Save Changes.

Após salvar as alterações, é possível observar o payload sendo executado e retornando como resposta “Arbitrary deserialization”.

Análise da vulnerabilidade
Ao identificar a utilização da função unserialize, foi necessário pensar em explorar a sequência de funções antecedentes e encontrar o endpoint raíz.
Através da análise do código fonte do plugin, foi possível chegar a uma ajax action no arquivo wdt_admin_ajax_actions.php que dava início a sequência de chamadas necessárias para alcançar a função serializedPhpRenderData.

Esta ajax action chama a função wdtSaveTableWithColumns que recebe um parâmetro POST table e que ao final da função invoca a função saveTableConfig passando o valor do table como um JSON.
Entrando na função saveTableConfig do arquivo class.wdtconfigcontroller.php, foi possível identificar que a função chama tryCreateTable passando os valores table_type e content do JSON passado no parâmetro POST citado anteriormente. Vale ressaltar que, foi preciso definir o valor da chave table_type do JSON como “serialized” para a desserialização ser engatilhada nas funções posteriores e o valor da chave content como a URL do webserver contendo o nosso payload.

Posteriormente, analisando a função tryCreateTable do arquivo class.wdtconfigcontroller.php foi possível identificar que é instanciada uma variável chamada $tableData onde será definida algumas propriedades baseadas nos valores que passamos por POST anteriormente, e, em seguida, ser passada na função fillFromData na linha 708.

Já na função fillFromData do arquivo class.wpdatatable.phpé possível identificar na linha 2268 um switch condicional que verifica o valor de $tableData->table_type. Se o valor for serialized então ele passa o content para a função serializedPHPBasedConstruct, como pode ser visto na linha 2296.

Entrando na função serializedPHPBasedConstruct do arquivo class.wpdatatable.php é possível perceber que o primeiro parâmetro da função é chamado $url, assim, é conclusivo que o content é utilizado como uma URL posteriormente. Esse parâmetro é passado na função sourceRenderData na linha 1647.

Fazendo uma análise da função sourceRenderData do arquivo class.wpdatatable.php foi possível identificar que na linha 1799 é verificado se o sourceType é serialized e sem seguida passado o parâmetro $source (nossa URL) na função serializedPhpRenderData.

Ao explorar a função serializedPhpRenderData do mesmo arquivo, é possível identificar que na segunda linha é realizada uma requisição para a nossa URL, salvo em uma variável chamada $serialized_content, e, em seguida, desserializada na nossa função alvo que é a unserialize.

Conclusivamente, a ordem da sequência de funções seria essa:
- add_action(‘wp_ajax_wpdatatables_save_table_config’, ‘wdtSaveTableWithColumns’);
- saveTableConfig
- WDTConfigController::tryCreateTable
- $tbl->fillFromData($tableData, array())
- $this->serializedPHPBasedConstruct(…
- $PHPArray = self::sourceRenderData($this, ‘serialized’, $url);
- $sourceArray = self::serializedPhpRenderData($source, $wpId);
- unserialize
Impacto
A vulnerabilidade requer um privilégio mínimo de administrador no WordPress, além disso, ela permite que sejam realizados ataques em um ambiente multisite. O multisite é um recurso especial do WordPress que as empresas e marcas utilizam para gerenciar vários sites a partir de um único painel do CMS (Content Management System), ou seja, é possível ampliar a invasão para outros sites além do atual.
Além disso, caso seja encontrado um gadget chain apropriado, é possível obter execução remota de comandos.
Mitigação
Neste contexto, podem existir diversas maneiras de mitigar essa vulnerabilidade, algumas delas são:
Caso seja necessário a utilização do unserialize:
- Realizar filtragem na URL de origem por whitelist
- Realizar filtragem no conteúdo da resposta da URL por whitelist, por exemplo, verificar previamente qual classe ou tipo de dado que está prestes a ser instanciado na desserialização.
- Não permitir que a desserialização seja induzida por valores de entradas vindas de usuários.
- Definir a propriedade allowed_classes como false para evitar a instanciação de classes.
Outras alternativas ao unserialize:
- Não utilizar unserialize e desenvolver um sistema próprio de serialização de dados para um tipo específico de dados, caso seja consentido que apenas um tipo de estrutura, seja utilizado.
Referências
- A8:2017-Insecure Deserialization
- Unserialize
- wpDataTables < 2.1.66 – Admin+ PHP Object Injection
- Desserialização Insegura em PHP: uma introdução
