CVE-2023-44396 (published 2024-04-15T18:15Z)

iTop is an IT service management platform.  Dashlet edits ajax endpoints can be used to produce XSS. Fixed in iTop 2.7.10, 3.0.4, and 3.1.1.\n


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/Combodo/iTop
commit_details: repo_url: https://github.com/Combodo/iTop
author: Pierre Goiffon
author_date: 2023-10-13 16:52:46+02:00
msg: N°6606 Backport of utils::ENUM_SANITIZATION_FILTER_* constants
Were introduced in 3.0.0, but not added to the support/2.7 branch
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: utils.inc.php
change_type: ModificationType.MODIFY
diff: @@ -45,6 +45,65 @@ class FileUploadException extends Exception
  */
 class utils
 {
+	/**
+	 * @var string
+	 * @since 2.7.10 3.0.0
+	 */
+	public const ENUM_SANITIZATION_FILTER_INTEGER = 'integer';
+	/**
+	 * @var string
+	 * @since 2.7.10 3.0.0
+	 */
+	public const ENUM_SANITIZATION_FILTER_CLASS = 'class';
+	/**
+	 * @var string
+	 * @since 2.7.10 3.0.0
+	 */
+	public const ENUM_SANITIZATION_FILTER_STRING = 'string';
+	/**
+	 * @var string
+	 * @since 2.7.10 3.0.0
+	 */
+	public const ENUM_SANITIZATION_FILTER_CONTEXT_PARAM = 'context_param';
+	/**
+	 * @var string
+	 * @since 2.7.10 3.0.0
+	 */
+	public const ENUM_SANITIZATION_FILTER_PARAMETER = 'parameter';
+	/**
+	 * @var string
+	 * @since 2.7.10 3.0.0
+	 */
+	public const ENUM_SANITIZATION_FILTER_FIELD_NAME = 'field_name';
+	/**
+	 * @var string
+	 * @since 2.7.10 3.0.0
+	 */
+	public const ENUM_SANITIZATION_FILTER_TRANSACTION_ID = 'transaction_id';
+	/**
+	 * @var string For XML / HTML node identifiers
+	 * @since 2.7.10 3.0.0
+	 */
+	public const ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER = 'element_identifier';
+	/**
+	 * @var string
+	 * @since 2.7.10 3.0.0
+	 */
+	public const ENUM_SANITIZATION_FILTER_RAW_DATA = 'raw_data';
+	/**
+	 * @var string
+	 * @since 3.0.2 3.1.0 N°4899
+	 * @since 2.7.10 N°6606
+	 */
+	public const ENUM_SANITIZATION_FILTER_URL = 'url';
+
+	/**
+	 * @var string
+	 * @since 3.0.0
+	 * @since 2.7.10 N°6606
+	 */
+	public const DEFAULT_SANITIZATION_FILTER = self::ENUM_SANITIZATION_FILTER_RAW_DATA;
+
 	/**
 	 * Cache when getting config from disk or set externally (using {@link SetConfig})
 	 * @internal
@@ -275,8 +334,7 @@ class utils
 
 	/**
 	 * @param string|string[] $value
-	 * @param string $sSanitizationFilter one of : integer, class, string, context_param, parameter, field_name,
-	 *               element_identifier, transaction_id, parameter, raw_data
+	 * @param string $sSanitizationFilter one of utils::ENUM_SANITIZATION_* const
 	 *
 	 * @return string|string[]|bool boolean for :
 	 *   * the 'class' filter (true if valid, false otherwise)
@@ -285,16 +343,19 @@ class utils
 	 * @since 2.5.2 2.6.0 new 'transaction_id' filter
 	 * @since 2.7.0 new 'element_identifier' filter
 	 * @since 2.7.7, 3.0.2, 3.1.0 N°4899 - new 'url' filter
+	 * @since 2.7.10 N°6606 use the utils::ENUM_SANITIZATION_* const
+	 *
+	 * @link https://www.php.net/manual/en/filter.filters.sanitize.php PHP sanitization filters
 	 */
 	protected static function Sanitize_Internal($value, $sSanitizationFilter)
 	{
 		switch ($sSanitizationFilter)
 		{
-			case 'integer':
+			case static::ENUM_SANITIZATION_FILTER_INTEGER:
 				$retValue = filter_var($value, FILTER_SANITIZE_NUMBER_INT);
 				break;
 
-			case 'class':
+			case static::ENUM_SANITIZATION_FILTER_CLASS:
 				$retValue = $value;
 				if (!MetaModel::IsValidClass($value))
 				{
@@ -302,14 +363,14 @@ class utils
 				}
 				break;
 
-			case 'string':
+			case static::ENUM_SANITIZATION_FILTER_STRING:
 				$retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
 				break;
 
-			case 'context_param':
-			case 'parameter':
-			case 'field_name':
-			case 'transaction_id':
+			case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
+			case static::ENUM_SANITIZATION_FILTER_PARAMETER:
+			case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
+			case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
 				if (is_array($value))
 				{
 					$retValue = array();
@@ -327,7 +388,7 @@ class utils
 				{
 					switch ($sSanitizationFilter)
 					{
-						case 'transaction_id':
+						case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
 							// same as parameter type but keep the dot character
 							// see N°1835 : when using file transaction_id on Windows you get *.tmp tokens
 							// it must be included at the regexp beginning otherwise you'll get an invalid character error
@@ -335,18 +396,18 @@ class utils
 								array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/')));
 							break;
 
-						case 'parameter':
+						case static::ENUM_SANITIZATION_FILTER_PARAMETER:
 							$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
 								array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F'
 							// characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
 							break;
 
-						case 'field_name':
+						case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
 							$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
 								array("options" => array("regexp" => '/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
 							break;
 
-						case 'context_param':
+						case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
 							$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
 								array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/')));
 							break;
@@ -356,18 +417,18 @@ class utils
 				break;
 
 			// For XML / HTML node identifiers
-			case 'element_identifier':
+			case static::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER:
 				$retValue = preg_replace('/[^a-zA-Z0-9_]/', '', $value);
 				break;
 
 			// For URL
-			case 'url':
+			case static::ENUM_SANITIZATION_FILTER_URL:
                 // N°6350 - returns only valid URLs
 				$retValue = filter_var($value, FILTER_VALIDATE_URL);
 				break;
 
 			default:
-			case 'raw_data':
+			case static::ENUM_SANITIZATION_FILTER_RAW_DATA:
 				$retValue = $value;
 			// Do nothing
 		}

programming_language: PHP
repo_url: https://github.com/Combodo/iTop
commit_details: repo_url: https://github.com/Combodo/iTop
author: Pierre Goiffon
author_date: 2023-10-02 16:11:00+02:00
msg: N°6606 security 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: utils.inc.php
change_type: ModificationType.MODIFY
diff: @@ -51,10 +51,19 @@ class utils
 	 */
 	public const ENUM_SANITIZATION_FILTER_INTEGER = 'integer';
 	/**
+	 * Datamodel class
 	 * @var string
 	 * @since 2.7.10 3.0.0
+	 * @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6606 update PHPDoc
+	 * @uses MetaModel::IsValidClass()
 	 */
 	public const ENUM_SANITIZATION_FILTER_CLASS = 'class';
+	/**
+	 * @var string
+	 * @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6606
+	 * @uses class_exists()
+	 */
+	public const ENUM_SANITIZATION_FILTER_PHP_CLASS = 'php_class';
 	/**
 	 * @var string
 	 * @since 2.7.10 3.0.0
@@ -344,6 +353,7 @@ class utils
 	 * @since 2.7.0 new 'element_identifier' filter
 	 * @since 2.7.7, 3.0.2, 3.1.0 N°4899 - new 'url' filter
 	 * @since 2.7.10 N°6606 use the utils::ENUM_SANITIZATION_* const
+	 * @since 2.7.10 N°6606 new case for ENUM_SANITIZATION_FILTER_PHP_CLASS
 	 *
 	 * @link https://www.php.net/manual/en/filter.filters.sanitize.php PHP sanitization filters
 	 */
@@ -367,6 +377,13 @@ class utils
 				$retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
 				break;
 
+			case static::ENUM_SANITIZATION_FILTER_PHP_CLASS:
+				$retValue = $value;
+				if (!class_exists($value)) {
+					$retValue = false;
+				}
+				break;
+
 			case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
 			case static::ENUM_SANITIZATION_FILTER_PARAMETER:
 			case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:

programming_language: PHP
filename: ajax.render.php
change_type: ModificationType.MODIFY
diff: @@ -1317,9 +1317,9 @@ EOF
 		case 'new_dashlet_id':
 			$sDashboardDivId = utils::ReadParam("dashboardid");
 			$bIsCustomized = true; // Only called at runtime when customizing a dashboard
-			$iRow = utils::ReadParam("iRow");
-			$iCol = utils::ReadParam("iCol");
-			$sDashletIdOrig = utils::ReadParam("dashletid");
+			$iRow = utils::ReadParam("iRow", 0, false, utils::ENUM_SANITIZATION_FILTER_INTEGER);
+			$iCol = utils::ReadParam("iCol", 0, false, utils::ENUM_SANITIZATION_FILTER_INTEGER);
+			$sDashletIdOrig = utils::ReadParam("dashletid", '', false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
 			$sFinalDashletId = Dashboard::GetDashletUniqueId($bIsCustomized, $sDashboardDivId, $iRow, $iCol, $sDashletIdOrig);
 			$oPage = new ajax_page('');
 			$oPage->add($sFinalDashletId);
@@ -1328,8 +1328,8 @@ EOF
 		case 'new_dashlet':
 			require_once(APPROOT.'application/forms.class.inc.php');
 			require_once(APPROOT.'application/dashlet.class.inc.php');
-			$sDashletClass = utils::ReadParam('dashlet_class', '');
-			$sDashletId = utils::ReadParam('dashlet_id', '', false, 'raw_data');
+			$sDashletClass = utils::ReadParam('dashlet_class', '', false, utils::ENUM_SANITIZATION_FILTER_PHP_CLASS);
+			$sDashletId = utils::ReadParam('dashlet_id', '', false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
 			if (is_subclass_of($sDashletClass, 'Dashlet'))
 			{
 				$oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId);
@@ -1352,13 +1352,14 @@ EOF
 		case 'update_dashlet_property':
 			require_once(APPROOT.'application/forms.class.inc.php');
 			require_once(APPROOT.'application/dashlet.class.inc.php');
-			$aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data');
-			$aParams = utils::ReadParam('params', '', false, 'raw_data');
-			$sDashletClass = $aParams['attr_dashlet_class'];
-			$sDashletType = $aParams['attr_dashlet_type'];
-			$sDashletId = utils::HtmlEntities($aParams['attr_dashlet_id']);
-			$aUpdatedProperties = $aParams['updated']; // Code of the changed properties as an array: 'attr_xxx', 'attr_xxy', etc...
-			$aPreviousValues = $aParams['previous_values']; // hash array: 'attr_xxx' => 'old_value'
+			$aExtraParams = utils::ReadParam('extra_params', array(), false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
+			$aParams = utils::ReadParam('params', [], false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); // raw_data because we need different filter depending on the options
+			$sDashletClass = utils::Sanitize($aParams['attr_dashlet_class'], DashletUnknown::class, utils::ENUM_SANITIZATION_FILTER_PHP_CLASS); // Dashlet PHP class or DashletUnknown if impl isn't present in the installed extensions
+			$sDashletType = utils::Sanitize($aParams['attr_dashlet_type'], '', utils::ENUM_SANITIZATION_FILTER_PHP_CLASS); // original Dashlet PHP class, could be non-existing on the iTop instance (XML definition loading)
+			$sDashletId = utils::Sanitize($aParams['attr_dashlet_id'], '', utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
+			$aUpdatedProperties = utils::Sanitize($aParams['updated'], [], utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); // Code of the changed properties as an array: 'attr_xxx', 'attr_xxy' etc
+			$aPreviousValues = utils::Sanitize($aParams['previous_values'], [], utils::ENUM_SANITIZATION_FILTER_RAW_DATA); // hash array: 'attr_xxx' => 'old_value' - no sanitization as values will be handled in the Dashlet object
+
 			if (is_subclass_of($sDashletClass, 'Dashlet'))
 			{
 				/** @var \Dashlet $oDashlet */

programming_language: PHP