
VTiger 오픈소스 CRM v7.5의 인증된 SQL 인젝션 취약점
발견자: Jacob Elliott
2023년 7월 13일
VTiger CRM v7.5.0의 보고서 모듈에서는 보고서에 대해 선택된 필드에 대한 검사가 충분하지 않아, 해당 필드가 저장된 후 보고서가 실행될 때 2차 SQL 인젝션으로 재도입됩니다. 이를 통해 공격자는 데이터베이스에서 사용자 비밀번호 해시, 웹서비스 API 액세스 키 및 기타 민감한 데이터를 포함한 임의의 필드를 유출할 수 있습니다.
CRM에 인증한 후, 사용자는 보고서 모듈로 이동하여 새 보고서를 생성할 수 있습니다.

테이블이 조인되는 방식 때문에, 레코드가 있는 모듈을 기본 모듈로 선택하는 것이 가장 잘 작동하는 것으로 보입니다. 저는 하나의 레코드를 포함하고 있는 연락처(Contacts)를 선택했습니다.

다음으로, 사용자는 기본 모듈에서 적법한 필드를 선택하고 보고서 생성 과정을 계속할 수 있습니다.

마지막으로, 사용자는 BurpSuite와 같은 프록시 도구로 연결을 가로채면서 최종 보고서를 저장하는 버튼을 클릭할 수 있습니다. selected_fields 매개변수에서 이전에 선택된 필드가 다음 형식으로 저장 함수에 전달됩니다.
sql_table:sql_column:label:field_name
이 시점에서 사용자는 sql_table과 sql_column을 데이터베이스에서 유출하려는 임의의 값으로 수정할 수 있습니다. 이 POC에서는 다음을 사용했습니다.
vtiger_users:user_name:Contacts_Salutation:salutationtype
및
vtiger_users:user_password:Contacts_First_Name:firstname
수정된 요청을 전달하면 최종 보고서가 표시되며, 데이터베이스에서 원하는 열을 포함하여 관리자 사용자의 사용자 이름과 비밀번호 해시가 드러납니다.

적절한 검사의 부재는 modules/Reports/ReportRun.php (394-398행)에서 도입됩니다. 제공된 각 열 이름은 ":"으로 분할됩니다.
$selectedfields = explode(":", $fieldcolname);
그런 다음 사용자가 관리자가 아닌 경우, 스크립트는 필드가 보고서를 위해 선택된 기본 모듈에서 생성된 허용된 필드 배열에 있는지 확인합니다.
!in_array($selectedfields[3], $permitted_fields[$module])
그러나 주어진 입력을 기억하십시오:
vtiger_users:user_name:Contacts_Salutation:salutationtype
"허용된 필드"가 배열의 인덱스 3에 있는 요소와 비교되기 때문에, 확인되는 필드는 연락처 모듈의 salutationtype이며, 이는 민감하지 않은 것으로 간주되어 내보내기가 허용됩니다. 그러나 배열의 처음 두 요소에 제공된 테이블과 열은 이러한 검증을 거치지 않아 데이터 노출로 이어집니다.
이 문제는 이 커밋에서 선택된 필드에 대한 유효성 검사를 변경하여 각 모듈에 하드코딩된 허용 필드와 비교하도록 함으로써 수정되었습니다.
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;
}