
Vulnerabilidade de Injeção SQL Autenticada no VTiger Open Source CRM v7.5
Descoberto por: Jacob Elliott
13/07/23
No módulo Reports do VTiger CRM v7.5.0, há uma verificação insuficiente dos campos selecionados para o relatório, que são armazenados e posteriormente reintroduzidos como uma Injeção SQL de segunda ordem quando o relatório é executado. Isso permite que o atacante vaze campos arbitrários do banco de dados, incluindo hashes de senhas de usuários, chaves de acesso da API do webservice e outros dados sensíveis.
Após autenticar-se no CRM, o usuário pode navegar até o módulo Reports e criar um novo relatório.

Devido à forma como as tabelas são unidas, parece funcionar melhor escolher um módulo que contenha registos como módulo primário. Escolhi Contacts, que continha um registo.

Em seguida, o usuário pode selecionar quaisquer campos legítimos do módulo primário e continuar o processo de criação do relatório.

Finalmente, o usuário pode clicar no botão para guardar o relatório final, enquanto interceta as conexões com uma ferramenta de proxy como o BurpSuite. No parâmetro selected_fields, os campos previamente selecionados são passados para a função de guardar no formato:
sql_table:sql_column:label:field_name
Neste ponto, o usuário pode modificar o sql_table e o sql_column para quaisquer valores arbitrários que deseje vazar do banco de dados. Para esta POC, usei:
vtiger_users:user_name:Contacts_Salutation:salutationtype
e
vtiger_users:user_password:Contacts_First_Name:firstname
Depois de encaminhar o pedido modificado, somos apresentados ao relatório final que contém as colunas desejadas do banco de dados, revelando o nome de usuário e o hash da senha do usuário administrador.

A falta de verificação adequada é introduzida em modules/Reports/ReportRun.php (linhas 394-398). Cada um dos nomes de coluna fornecidos é dividido por “:”.
$selectedfields = explode(":", $fieldcolname);
E então, se o usuário não for um administrador, o script verifica se o campo está num array de campos permitidos, que é gerado a partir do módulo primário selecionado para o relatório:
!in_array($selectedfields[3], $permitted_fields[$module])
No entanto, recorde o input que foi fornecido:
vtiger_users:user_name:Contacts_Salutation:salutationtype
Como os “campos permitidos” estão a ser verificados contra o elemento no índice 3 do array, o campo que está a ser verificado é salutationtype no módulo Contacts, que não é considerado sensível e, portanto, é permitido para exportação. No entanto, a tabela e a coluna fornecidas nos primeiros dois elementos do array não passam por essa verificação, levando à exposição dos dados.
Este problema foi corrigido neste commit ao alterar a validação dos campos selecionados para que sejam verificados contra campos permitidos codificados em 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;
}