CVE-2022-24780 (PHP, published 2022-04-05T19:15Z)
Description: value: Combodo iTop is a web based IT Service Management tool. In versions prior to 2.7.6 and 3.0.0, users of the iTop user portal can send TWIG code to the server by forging specific http queries, and execute arbitrary code on the server using http server user privileges. This issue is fixed in versions 2.7.6 and 3.0.0. There are currently no known workarounds.
Severity: MEDIUM (CVSS v3 base score: 8.8)
Weakness: CWE-94 \u2014 Improper Control of Generation of Code ('Code Injection')
The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
Repository: https://github.com/Combodo/iTop
Files changed:
datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php
datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php
sources/form/formmanager.class.inc.php
Commit message: N°4384 Security hardening


Vulnerable code:
	static function FromJSON($sJson)
		if (is_array($sJson))
		{
		}
		else
		{
		if (!isset($aJson['formobject_class']))
		{
		if (!isset($aJson['formcallbacks']))
		{
	 * @param string                                    $sMode
	 * @param string                                    $sObjectClass
	 * @param string                                    $sObjectId
	 * @param string                                    $aFormProperties
			$aFormProperties = ApplicationHelper::GetLoadedFormFromClass($this->aCombodoPortalInstanceConf['forms'], $sObjectClass, $sMode);
				// Check the number of editable fields
		}
		else
		{
			if (empty($sFormManagerClass) || empty($sFormManagerData))
			{
			/** @var \Combodo\iTop\Portal\Form\ObjectFormManager $oFormManager */
			$oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
			if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
			{
	 * @param string $sJson

Fixed code:
	 * @param bool $bTrustContent if false then won't allow TWIG content
	 *
	 * @throws \SecurityException if twig content is present and $bTrustContent is false
	public static function FromJSON($sJson, $bTrustContent = false)
		if (is_array($sJson)) {
		} else {
		if (false === $bTrustContent) {
			/** @noinspection NestedPositiveIfStatementsInspection */
			if (isset($aJson['formproperties']['layout']['type']) && ($aJson['formproperties']['layout']['type'] === 'twig')) {
				// There will be an IssueLog above in the hierarchy due to the exception, but we are logging here so that we can output the JSON data !
				IssueLog::Error('Portal received a query with forbidden twig content!', \LogChannels::PORTAL, ['formmanager_data' => $aJson]);
				throw new \SecurityException('Twig content not allowed in this context!');
			}
		}

		if (!isset($aJson['formobject_class'])) {
		if (!isset($aJson['formcallbacks'])) {
	/**
	 * @param string $sPostedFormManagerData received data from the browser
	 * @param array $aOriginalFormProperties data generated server side
	 *
	 * @return bool true if the data are identical
	 *
	 * @since 2.7.6 3.0.0 N°4384 check formmanager_data
	 */
	public static function CanTrustFormLayoutContent($sPostedFormManagerData, $aOriginalFormProperties)
	{
		$aPostedFormManagerData = json_decode($sPostedFormManagerData, true);
		$sPostedFormLayoutType = (isset($aPostedFormManagerData['formproperties']['layout']['type'])) ? $aPostedFormManagerData['formproperties']['layout']['type'] : '';

		if ($sPostedFormLayoutType === 'xhtml') {
			return true;
		}

		// we need to parse the content so that autoclose tags are returned correctly (`<div />` => `<div></div>`)
		$oHtmlDocument = new \DOMDocument();

		$sPostedFormLayoutContent = (isset($aPostedFormManagerData['formproperties']['layout']['content'])) ? $aPostedFormManagerData['formproperties']['layout']['content'] : '';
		$oHtmlDocument->loadXML('<root>'.$sPostedFormLayoutContent.'</root>');
		$sPostedFormLayoutRendered = $oHtmlDocument->saveHTML();

		$sOriginalFormLayoutContent = (isset($aOriginalFormProperties['layout']['content'])) ? $aOriginalFormProperties['layout']['content'] : '';
		$oHtmlDocument->loadXML('<root>'.$sOriginalFormLayoutContent.'</root>');
		$sOriginalFormLayoutContentRendered = $oHtmlDocument->saveHTML();

		return ($sPostedFormLayoutRendered === $sOriginalFormLayoutContentRendered);
	}

	 * @param string $sMode
	 * @param string $sObjectClass
	 * @param string $sObjectId
	 * @param array $aFormProperties
		$aOriginalFormProperties = ApplicationHelper::GetLoadedFormFromClass($this->aCombodoPortalInstanceConf['forms'], $sObjectClass, $sMode);
			$aFormProperties = $aOriginalFormProperties;
			// Check the number of editable fields
		} else {
			/** @var \Combodo\iTop\Portal\Form\ObjectFormManager $sFormManagerClass */
			if (empty($sFormManagerClass) || empty($sFormManagerData)) {
			$bTrustContent = $sFormManagerClass::CanTrustFormLayoutContent($sFormManagerData, $aOriginalFormProperties);
			$oFormManager = $sFormManagerClass::FromJSON($sFormManagerData, $bTrustContent);
			if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== '')) {
	 * @param string|string[] $sJson
	 *