
Vulnérabilité d'injection SQL authentifiée dans VTiger Open Source CRM v7.5
Découvert par : Jacob Elliott
13/07/23
Dans le module Rapports de VTiger CRM v7.5.0, une vérification insuffisante des champs sélectionnés pour le rapport est effectuée, stockés puis réintroduits sous la forme d'une injection SQL de second ordre lorsque le rapport est exécuté. Cela permet à l'attaquant de divulguer des champs arbitraires de la base de données, y compris les hachages de mots de passe utilisateur, les clés d'accès API des services web, et d'autres données sensibles.
Après authentification auprès du CRM, l'utilisateur peut naviguer vers le module Rapports et créer un nouveau rapport.

En raison de la façon dont les tables sont jointes, il semble préférable de choisir comme module principal un module contenant des enregistrements. J'ai choisi Contacts, qui contenait un enregistrement.

Ensuite, l'utilisateur peut sélectionner des champs légitimes du module principal et poursuivre le processus de création du rapport.

Enfin, l'utilisateur peut cliquer sur le bouton pour enregistrer le rapport final, tout en interceptant les connexions avec un outil proxy comme BurpSuite. Dans le paramètre selected_fields, les champs précédemment sélectionnés sont transmis à la fonction de sauvegarde au format :
sql_table:sql_column:label:field_name
À ce stade, l'utilisateur peut modifier sql_table et sql_column pour y mettre les valeurs arbitraires qu'il souhaite extraire de la base de données. Pour cette POC, j'ai utilisé :
vtiger_users:user_name:Contacts_Salutation:salutationtype
et
vtiger_users:user_password:Contacts_First_Name:firstname
Après avoir transmis la requête modifiée, le rapport final s'affiche contenant les colonnes souhaitées de la base de données, révélant le nom d'utilisateur et le hachage du mot de passe de l'administrateur.

L'absence de vérification appropriée se trouve dans modules/Reports/ReportRun.php (lignes 394-398). Chaque nom de colonne fourni est divisé sur « : ».
$selectedfields = explode(":", $fieldcolname);
Et ensuite, si l'utilisateur n'est pas administrateur, le script vérifie si le champ se trouve dans un tableau de champs autorisés généré à partir du module principal sélectionné pour le rapport :
!in_array($selectedfields[3], $permitted_fields[$module])
Cependant, rappelons l'entrée qui a été fournie :
vtiger_users:user_name:Contacts_Salutation:salutationtype
Étant donné que les « champs autorisés » sont vérifiés par rapport à l'élément à l'indice 3 du tableau, le champ vérifié est salutationtype dans le module Contacts, qui n'est pas considéré comme sensible et est donc autorisé à l'exportation. Cependant, la table et la colonne fournies dans les deux premiers éléments du tableau ne subissent aucune vérification de ce type, ce qui entraîne l'exposition des données.
Ce problème a été corrigé dans ce commit en modifiant la validation des champs sélectionnés afin qu'ils soient vérifiés par rapport aux champs autorisés codés en dur dans chaque module.
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;
}