From 9df92665e08c4bf5d4d8a5a9fe21fd3fb26fb273 Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Fri, 13 Oct 2023 16:52:46 +0200 Subject: [PATCH 1/2] =?UTF-8?q?N=C2=B06606=20Backport=20of=20utils::ENUM?= =?UTF-8?q?=5FSANITIZATION=5FFILTER=5F*=20constants=20Were=20introduced=20?= =?UTF-8?q?in=203.0.0,=20but=20not=20added=20to=20the=20support/2.7=20bran?= =?UTF-8?q?ch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/utils.inc.php | 93 ++++++++++++++++++++++++++++++++------- 1 file changed, 77 insertions(+), 16 deletions(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index 15195c1f3..01c87eb3e 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -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 } From c72cb7e70ebf469ce0ec01f5f9b524e39afe6c7f Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Mon, 2 Oct 2023 16:11:00 +0200 Subject: [PATCH 2/2] =?UTF-8?q?N=C2=B06606=20security=20hardening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/utils.inc.php | 17 +++++++++++++++++ pages/ajax.render.php | 25 +++++++++++++------------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index 01c87eb3e..7133e20fa 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -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: diff --git a/pages/ajax.render.php b/pages/ajax.render.php index e61182b1f..2d63f1acf 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -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 */