
Vulnerabilidad de inyección SQL autenticada en VTiger Open Source CRM v7.5
Descubierto por: Jacob Elliott
13/07/23
En el módulo de Informes de VTiger CRM v7.5.0, la verificación de los campos seleccionados para el informe es insuficiente; estos campos se almacenan y luego se reintroducen como una inyección SQL de segundo orden cuando se ejecuta el informe. Esto permite al atacante filtrar campos arbitrarios de la base de datos, incluidos los hash de contraseñas de usuarios, claves de acceso a la API del servicio web y otros datos confidenciales.
Después de autenticarse en el CRM, el usuario puede ir al módulo de Informes y crear un nuevo informe.

Debido a la forma en que se unen las tablas, parece funcionar mejor elegir como módulo principal un módulo que tenga registros. Elegí Contacts, que contenía un registro.

A continuación, el usuario puede seleccionar cualquier campo legítimo del módulo principal y continuar con el proceso de creación del informe.

Finalmente, el usuario puede hacer clic en el botón para guardar el informe final, mientras intercepta las conexiones con una herramienta proxy como BurpSuite. En el parámetro selected_fields, los campos seleccionados previamente se pasan a la función de guardado con el formato:
sql_table:sql_column:label:field_name
En este punto, el usuario puede modificar sql_table y sql_column a cualquier valor arbitrario que desee filtrar de la base de datos. Para esta POC, utilicé:
vtiger_users:user_name:Contacts_Salutation:salutationtype
y
vtiger_users:user_password:Contacts_First_Name:firstname
Después de reenviar la solicitud modificada, se nos presenta el informe final que contiene las columnas deseadas de la base de datos, revelando el nombre de usuario y el hash de la contraseña del usuario administrador.

La falta de verificación adecuada se introduce en modules/Reports/ReportRun.php (líneas 394-398). Cada uno de los nombres de columna proporcionados se divide por “:”.
$selectedfields = explode(":", $fieldcolname);
Y luego, si el usuario no es administrador, el script comprueba si el campo está en una matriz de campos permitidos que se genera a partir del módulo principal seleccionado para el informe:
!in_array($selectedfields[3], $permitted_fields[$module])
Sin embargo, recuerde la entrada que se proporcionó:
vtiger_users:user_name:Contacts_Salutation:salutationtype
Debido a que los “campos permitidos” se comparan con el elemento en el índice 3 de la matriz, el campo que se verifica es salutationtype en el módulo Contacts, que no se considera confidencial y, por lo tanto, está permitido para la exportación. Sin embargo, la tabla y la columna proporcionadas en los dos primeros elementos de la matriz no pasan por dicha verificación, lo que conduce a la exposición de los datos.
Este problema se corrigió en este commit cambiando la validación de los campos seleccionados para que se comparen con los campos permitidos codificados en cada módulo.
public function checkPermission(Vtiger_Request $request) {
parent::checkPermission($request);
$record = $request->get('record');
if ($record) {
$reportModel = Reports_Record_Model::getCleanInstance($record);
if (!$reportModel->isEditable()) {
throw new AppException(vtranslate('LBL_PERMISSION_DENIED'));
}
}
$selectedFields = $request->get('selected_fields');
$groupbyfields = $request->get('groupbyfield');
$fieldsData = array($selectedFields, $groupbyfields);
foreach ($fieldsData as $selectedField){
foreach ($selectedField as $field) {
list($tablename, $colname, $module_field, $fieldname, $single) = split(":", $field);
list($module, $fieldName) = split("_", $module_field, 2);
$moduleModel = Vtiger_Module_Model::getInstance($module);
$fieldModel = Vtiger_Field_Model::getInstance($fieldname, $moduleModel);
if (($fieldModel->table !== $tablename) || ($fieldModel->column !== $colname)) {
throw new AppException(vtranslate('LBL_PERMISSION_DENIED'));
}
}
}
return true;
}