CVE-2023-44396: Insufficient Information (NVD-CWE-noinfo)
value: 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.

CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-15T18:15Z
Repository: https://github.com/Combodo/iTop
Language: PHP
Files changed: application/utils.inc.php, pages/ajax.render.php
Commit date: 2023-10-13 17:15:37 +0200
Commit message: N°6606 security hardening

Diff: diff --git a/application/utils.inc.php b/application/utils.inc.php
index 01c87eb3e3d..7133e20fa8e 100644
--- a/application/utils.inc.php
+++ b/application/utils.inc.php
@@ -49,14 +49,23 @@ class utils
 	 * @var string
 	 * @since 2.7.10 3.0.0
 	 */
 	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
 	 */
 	public const ENUM_SANITIZATION_FILTER_STRING = 'string';
@@ -342,10 +351,11 @@ 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
+	 * @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
 	 */
 	protected static function Sanitize_Internal($value, $sSanitizationFilter)
 	{
@@ -365,10 +375,17 @@ class utils
 
 			case static::ENUM_SANITIZATION_FILTER_STRING:
 				$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:
 			case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
 				if (is_array($value))
diff --git a/pages/ajax.render.php b/pages/ajax.render.php
index e61182b1f8c..2d63f1acf81 100644
--- a/pages/ajax.render.php
+++ b/pages/ajax.render.php
@@ -1315,23 +1315,23 @@ EOF
 			break;
 
 		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);
 			break;
 
 		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);
 				$offset = $oPage->start_capture();
 				$oDashlet->DoRender($oPage, true /* bEditMode */, false /* bEnclosingDiv */);
@@ -1350,17 +1350,18 @@ EOF
 			break;
 
 		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 */
 				$oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId);
 				$oDashlet->SetDashletType($sDashletType);

Vulnerable code: 			$iRow = utils::ReadParam("iRow");
			$iCol = utils::ReadParam("iCol");
			$sDashletIdOrig = utils::ReadParam("dashletid");
			$sDashletClass = utils::ReadParam('dashlet_class', '');
			$sDashletId = utils::ReadParam('dashlet_id', '', false, 'raw_data');
			$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'
Fixed code: 	 * Datamodel class
	 * @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6606 update PHPDoc
	 * @uses MetaModel::IsValidClass()
	/**
	 * @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';
	 * @since 2.7.10 N°6606 new case for ENUM_SANITIZATION_FILTER_PHP_CLASS
			case static::ENUM_SANITIZATION_FILTER_PHP_CLASS:
				$retValue = $value;
				if (!class_exists($value)) {
					$retValue = false;
				}
				break;

			$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);
			$sDashletClass = utils::ReadParam('dashlet_class', '', false, utils::ENUM_SANITIZATION_FILTER_PHP_CLASS);
			$sDashletId = utils::ReadParam('dashlet_id', '', false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
			$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