CVE-2021-32663 (published 2021-10-19T18:15Z)

iTop is an open source web based IT Service Management tool. In affected versions an attacker can call the system setup without authentication. Given specific parameters this can lead to SSRF. This issue has been resolved in versions 2.6.5 and 2.7.5 and later

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-918
cwe_name: Server-Side Request Forgery (SSRF)
description: The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
extended_description: By providing URLs to unexpected hosts or ports, attackers can make it appear that the server is sending the request, possibly bypassing access controls such as firewalls that prevent the attackers from accessing the URLs directly. The server can be used as a proxy to conduct port scanning of hosts in internal networks, use other URLs such as that can access documents on the system (using file://), or use other protocols such as gopher:// or tftp://, which may provide greater control over the contents of requests.
         

Fix information:
repo_url: https://github.com/Combodo/iTop
commit_details: repo_url: https://github.com/Combodo/iTop
author: Eric
author_date: 2021-05-27 09:29:50+02:00
msg: N°3952 - code hardening
repository: repo_url: https://github.com/Combodo/iTop
repo_name: Combodo/iTop
description: A simple, web based IT Service Management tool 
repo_language: PHP
owner: Combodo
file_changes: filename: ajax.dataloader.php
change_type: ModificationType.MODIFY
diff: @@ -147,12 +147,7 @@ header("Expires: Fri, 17 Jul 1970 05:00:00 GMT");    // Date in the past
 $sOperation = Utils::ReadParam('operation', '');
 try
 {
-	$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
-	if (!file_exists(APPROOT.'data/setup/authent') || $sAuthent !== file_get_contents(APPROOT.'data/setup/authent'))
-	{
-		throw new SecurityException('Setup operations are not allowed outside of the setup');
-		SetupPage::log_error("Setup operations are not allowed outside of the setup");
-	}
+	SetupUtils::CheckSetupToken();
 
 	switch($sOperation)
 	{
@@ -199,7 +194,6 @@ catch(Exception $e)
 {
 	header("HTTP/1.0 500 Internal server error.");
 	echo "<p>An error happened while processing the installation:</p>\n";
-	echo '<p>'.$e."</p>\n";
 	SetupPage::log_error("An error happened while processing the installation: ".$e);
 }
 
@@ -207,7 +201,7 @@ if (function_exists('memory_get_peak_usage'))
 {
 	if ($sOperation == 'file')
 	{
-		SetupPage::log_info("loading file '$sFileName', peak memory usage. ".memory_get_peak_usage());
+		SetupPage::log_info("loading file peak memory usage. ".memory_get_peak_usage());
 	}
 	else
 	{

programming_language: PHP
filename: index.php
change_type: ModificationType.MODIFY
diff: @@ -30,6 +30,7 @@ require_once(APPROOT.'/setup/setuppage.class.inc.php');
 require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
 require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
 
+session_start();
 clearstatcache(); // Make sure we know what we are doing !
 // Set a long (at least 4 minutes) execution time for the setup to avoid timeouts during this phase
 ini_set('max_execution_time', max(240, ini_get('max_execution_time')));
@@ -41,16 +42,14 @@ date_default_timezone_set('Europe/Paris'); // Just to avoid a warning if the tim
 /////////////////////////////////////////////////////////////////////
 // Fake functions to protect the first run of the installer
 // in case the PHP JSON module is not installed...
-if (!function_exists('json_encode'))
-{
+if (!function_exists('json_encode')) {
 	function json_encode($value, $options = null)
 	{
 		return '[]';
 	}
 }
-if (!function_exists('json_decode'))
-{
-	function json_decode($json, $assoc=null)
+if (!function_exists('json_decode')) {
+	function json_decode($json, $assoc = null)
 	{
 		return array();
 	}
@@ -58,4 +57,12 @@ if (!function_exists('json_decode'))
 /////////////////////////////////////////////////////////////////////
 
 $oWizard = new WizardController('WizStepWelcome');
-$oWizard->Run();
+//N°3952
+if (SetupUtils::IsSessionSetupTokenValid()) {
+	// Normal operation
+	$oWizard->Run();
+} else {
+	// Force initializing the setup
+	$oWizard->Start();
+	SetupUtils::CreateSetupToken();
+}

programming_language: PHP
filename: setuputils.class.inc.php
change_type: ModificationType.MODIFY
diff: @@ -1826,6 +1826,75 @@ EOF
 	{
 		return APPROOT.'log/setup-queries-'.strftime('%Y-%m-%d_%H_%M').'.sql';
 	}
+
+	/**
+	 * Create and store Setup authentication token
+	 *
+	 * @return string token
+	 */
+	public final static function CreateSetupToken()
+	{
+		if (!is_dir(APPROOT.'data'))
+		{
+			mkdir(APPROOT.'data');
+		}
+		if (!is_dir(APPROOT.'data/setup'))
+		{
+			mkdir(APPROOT.'data/setup');
+		}
+		$sUID = hash('sha256', rand());
+		file_put_contents(APPROOT.'data/setup/authent', $sUID);
+		$_SESSION['setup_token'] = $sUID;
+		return $sUID;
+	}
+
+	/**
+	 * Verify Setup authentication token (from the request parameter 'authent')
+	 *
+	 * @param bool $bRemoveToken
+	 *
+	 * @throws \SecurityException
+	 */
+	public final static function CheckSetupToken($bRemoveToken = false)
+	{
+		$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
+		$sTokenFile = APPROOT.'data/setup/authent';
+		if (!file_exists($sTokenFile) || $sAuthent !== file_get_contents($sTokenFile))
+		{
+			throw new SecurityException('Setup operations are not allowed outside of the setup');
+		}
+		if ($bRemoveToken)
+		{
+			@unlink($sTokenFile);
+		}
+	}
+
+	/**
+	 * Check setup transaction and create a new one if necessary
+	 *
+	 * @return bool
+	 */
+	public static function IsSessionSetupTokenValid()
+	{
+		if (isset($_SESSION['setup_token'])) {
+			$sAuth = $_SESSION['setup_token'];
+			$sTokenFile = APPROOT.'data/setup/authent';
+			if (file_exists($sTokenFile) && $sAuth === file_get_contents($sTokenFile)) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	public static function EraseSetupToken()
+	{
+		$sTokenFile = APPROOT.'data/setup/authent';
+		if (is_file($sTokenFile)) {
+			unlink($sTokenFile);
+		}
+		unset($_SESSION['setup_token']);
+	}
 }
 
 /**

programming_language: PHP
filename: wizardcontroller.class.inc.php
change_type: ModificationType.MODIFY
diff: @@ -105,7 +105,7 @@ class WizardController
 	/**
 	 * Starts the wizard by displaying it in its initial state
 	 */
-	protected function Start()
+	public function Start()
 	{
 		$sCurrentStepClass = $this->sInitialStepClass;
 		$oStep = new $sCurrentStepClass($this, $this->sInitialState);
@@ -121,7 +121,7 @@ class WizardController
 		$sCurrentState = utils::ReadParam('_state', $this->sInitialState);
 		/** @var \WizardStep $oStep */
 		$oStep = new $sCurrentStepClass($this, $sCurrentState);
-		if ($oStep->ValidateParams($sCurrentState))
+		if ($oStep->ValidateParams())
 		{
 			$this->PushStep(array('class' => $sCurrentStepClass, 'state' => $sCurrentState));
 			$aPossibleSteps = $oStep->GetPossibleSteps();
@@ -173,6 +173,7 @@ class WizardController
 				// The configuration file already exists
 				if (!is_writable($sConfigFile))
 				{
+					SetupUtils::EraseSetupToken();
 					$sRelativePath = utils::GetConfigFilePathRelative();
 					$oP = new SetupPage('Installation Cannot Continue');
 					$oP->add("<h2>Fatal error</h2>\n");
@@ -180,7 +181,8 @@ class WizardController
 					$oP->p("The wizard cannot modify the configuration file for you. If you want to upgrade ".ITOP_APPLICATION.", make sure that the file '<b>".$sRelativePath."</b>' can be modified by the web server.");
 					$oP->p('<button type="button" onclick="window.location.reload()">Reload</button>');
 					$oP->output();
-					return;
+					// Prevent token creation
+					exit;
 				}
 			}			
 		}

programming_language: PHP
filename: wizardsteps.class.inc.php
change_type: ModificationType.MODIFY
diff: @@ -57,16 +57,7 @@ class WizStepWelcome extends WizardStep
 
 	public function ProcessParams($bMoveForward = true)
 	{
-		if (!is_dir(APPROOT.'data'))
-		{
-			mkdir(APPROOT.'data');
-		}
-		if (!is_dir(APPROOT.'data/setup'))
-		{
-			mkdir(APPROOT.'data/setup');
-		}
-		$sUID = hash('sha256', rand());
-		file_put_contents(APPROOT.'data/setup/authent', $sUID);
+		$sUID = SetupUtils::CreateSetupToken();
 		$this->oWizard->SetParameter('authent', $sUID);
 		return array('class' => 'WizStepInstallOrUpgrade', 'state' => '');
 	}
@@ -2643,6 +2634,7 @@ class WizStepDone extends WizardStep
 		$oPage->add('<img style="border:0" src="'.$sImgUrl.'"/>');
 		$sForm = addslashes($sForm);
 		$oPage->add_ready_script("$('#wiz_form').after('$sForm');");
+		SetupUtils::EraseSetupToken();
 	}
 
 	public function CanMoveForward()

programming_language: PHP
repo_url: https://github.com/Combodo/iTop
commit_details: repo_url: https://github.com/Combodo/iTop
author: Eric
author_date: 2021-05-27 09:55:16+02:00
msg: N°3952 - code hardening (merged from support/2.6)
repository: repo_url: https://github.com/Combodo/iTop
repo_name: Combodo/iTop
description: A simple, web based IT Service Management tool 
repo_language: PHP
owner: Combodo