CVE-2023-4197: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') (CWE-74)
value: Improper input validation in Dolibarr ERP CRM <= v18.0.1 fails to strip certain PHP code from user-supplied input when creating a Website, allowing an attacker to inject and evaluate arbitrary PHP code.
CWE description: The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
CVSS v3 base score: 8.8
Published: 2023-11-01T08:15Z
Repository: https://github.com/Dolibarr/dolibarr
Language: PHP
Files changed: htdocs/core/lib/website.lib.php, test/phpunit/WebsiteTest.php
Commit date: 2023-09-05 00:49:01 +0200
Commit message: FIX #CVE-2023-4197
Diff: diff --git a/htdocs/core/lib/website.lib.php b/htdocs/core/lib/website.lib.php
index d27c4dac37619..30edeac0d32b8 100644
--- a/htdocs/core/lib/website.lib.php
+++ b/htdocs/core/lib/website.lib.php
@@ -74,10 +74,13 @@ function dolStripPhpCode($str, $replacewith = '')
  * @see dolStripPhpCode(), checkPHPCode()
  */
 function dolKeepOnlyPhpCode($str)
 {
 	$str = str_replace('<?=', '<?php', $str);
+	$str = str_replace('<?php', '__LTINTPHP__', $str);
+	$str = str_replace('<?', '<?php', $str);			// replace the short_open_tag. It is recommended to set this is Off in php.ini
+	$str = str_replace('__LTINTPHP__', '<?php', $str);
 
 	$newstr = '';
 
 	// Split on each opening tag
 	//$parts = explode('<?php', $str);
diff --git a/test/phpunit/WebsiteTest.php b/test/phpunit/WebsiteTest.php
index a0cbc5084c7d0..86d510ce8c87c 100644
--- a/test/phpunit/WebsiteTest.php
+++ b/test/phpunit/WebsiteTest.php
@@ -224,6 +224,29 @@ class WebsiteTest extends PHPUnit\Framework\TestCase
 		$s = '<?php $_="{"; $_=($_^"<").($_^">;").($_^"/"); ?><?=${\'_\'.$_}["_"](${\'_\'.$_}["__"]);?>';
 		$result = checkPHPCode('', $s);
 		print __METHOD__." result checkPHPCode=".$result."\n";
 		$this->assertEquals($result, 1, 'checkPHPCode did not detect the string was dangerous');
 	}
+
+	/**
+	 * testDolKeepOnlyPhpCode
+	 *
+	 * @return void
+	 */
+	public function testDolKeepOnlyPhpCode()
+	{
+		$s = 'HTML content <?php exec("eee"); ?> and more HTML content';
+		$result = dolKeepOnlyPhpCode($s);
+		print __METHOD__." result dolKeepOnlyPhpCode=".$result."\n";
+		$this->assertEquals('<?php exec("eee"); ?>', $result, 'dolKeepOnlyPhpCode did extract the correct string');
+
+		$s = 'HTML content <? exec("eee"); ?> and more HTML content';
+		$result = dolKeepOnlyPhpCode($s);
+		print __METHOD__." result dolKeepOnlyPhpCode=".$result."\n";
+		$this->assertEquals('<?php exec("eee"); ?>', $result, 'dolKeepOnlyPhpCode did extract the correct string');
+
+		$s = 'HTML content <?php test() <?php test2(); ?> and more HTML content';
+		$result = dolKeepOnlyPhpCode($s);
+		print __METHOD__." result dolKeepOnlyPhpCode=".$result."\n";
+		$this->assertEquals('<?php test() ?><?php test2(); ?>', $result, 'dolKeepOnlyPhpCode did extract the correct string');
+	}
 }

Fixed code: 	$str = str_replace('<?php', '__LTINTPHP__', $str);
	$str = str_replace('<?', '<?php', $str);			// replace the short_open_tag. It is recommended to set this is Off in php.ini
	$str = str_replace('__LTINTPHP__', '<?php', $str);

	/**
	 * testDolKeepOnlyPhpCode
	 *
	 * @return void
	 */
	public function testDolKeepOnlyPhpCode()
	{
		$s = 'HTML content <?php exec("eee"); ?> and more HTML content';
		$result = dolKeepOnlyPhpCode($s);
		print __METHOD__." result dolKeepOnlyPhpCode=".$result."\n";
		$this->assertEquals('<?php exec("eee"); ?>', $result, 'dolKeepOnlyPhpCode did extract the correct string');

		$s = 'HTML content <? exec("eee"); ?> and more HTML content';
		$result = dolKeepOnlyPhpCode($s);
		print __METHOD__." result dolKeepOnlyPhpCode=".$result."\n";
		$this->assertEquals('<?php exec("eee"); ?>', $result, 'dolKeepOnlyPhpCode did extract the correct string');

		$s = 'HTML content <?php test() <?php test2(); ?> and more HTML content';
		$result = dolKeepOnlyPhpCode($s);
		print __METHOD__." result dolKeepOnlyPhpCode=".$result."\n";
		$this->assertEquals('<?php test() ?><?php test2(); ?>', $result, 'dolKeepOnlyPhpCode did extract the correct string');
	}