diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 165d9c503a4..61f268d7871 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -7995,6 +7995,25 @@ function dol_eval($s, $returnvalue = 0, $hideerrors = 1) global $obj; // To get $obj used into list when dol_eval is used for computed fields and $obj is not yet $object global $soc; // For backward compatibility + // Replace dangerous char (used for RCE), we allow only PHP variable testing. + if (strpos($s, '`') !== false) { + return 'Bad string syntax to evaluate: '.$s; + } + + // We block using of php exec or php file functions + $forbiddenphpcommands = array("exec(", "passthru(", "shell_exec(", "system(", "proc_open(", "popen(", "eval(", "dol_eval(", "executeCLI("); + $forbiddenphpcommands = array_merge($forbiddenphpcommands, array("fopen(", "file_put_contents(", "fputs(", "fputscsv(", "fwrite(", "fpassthru(", "unlink(", "mkdir(", "rmdir(", "symlink(", "touch(", "umask(")); + $forbiddenphpcommands = array_merge($forbiddenphpcommands, array('function(', '$$', 'call_user_func(', '_SESSION', '_COOKIE')); + do { + $oldstringtoclean = $s; + $s = str_ireplace($forbiddenphpcommands, '__forbiddenstring__', $s); + //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\(/i', '', $s); // Remove $function( call and $mycall->mymethod( + } while ($oldstringtoclean != $s); + + if (strpos($s, '__forbiddenstring__') !== false) { + return 'Bad string syntax to evaluate: '.$s; + } + //print $s."
\n"; if ($returnvalue) { if ($hideerrors) { diff --git a/test/phpunit/SecurityTest.php b/test/phpunit/SecurityTest.php index f889a6c542b..b1a32249ffc 100644 --- a/test/phpunit/SecurityTest.php +++ b/test/phpunit/SecurityTest.php @@ -777,4 +777,48 @@ class SecurityTest extends PHPUnit\Framework\TestCase $result=dol_sanitizeFileName('bad file --evilparam'); $this->assertEquals('bad file _evilparam', $result); } + + /** + * testDolEval + * + * @return void + */ + public function testDolEval() + { + global $conf,$user,$langs,$db; + $conf=$this->savconf; + $user=$this->savuser; + $langs=$this->savlangs; + $db=$this->savdb; + + $result=dol_eval('1==1', 1, 0); + print "result = ".$result."\n"; + $this->assertTrue($result); + + $result=dol_eval('1==2', 1, 0); + print "result = ".$result."\n"; + $this->assertFalse($result); + + include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; + include_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php'; + $result=dol_eval('(($reloadedobj = new Task($db)) && ($reloadedobj->fetchNoCompute($object->id) > 0) && ($secondloadedobj = new Project($db)) && ($secondloadedobj->fetchNoCompute($reloadedobj->fk_project) > 0)) ? $secondloadedobj->ref: "Parent project not found"', 1, 1); + print "result = ".$result."\n"; + $this->assertEquals('Parent project not found', $result); + + $result=dol_eval('$a=function() { }; $a;', 1, 1); + print "result = ".$result."\n"; + $this->assertContains('Bad string syntax to evaluate', $result); + + $result=dol_eval('$a=exec("ls");', 1, 1); + print "result = ".$result."\n"; + $this->assertContains('Bad string syntax to evaluate', $result); + + $result=dol_eval('$a="test"; $$a;', 1, 0); + print "result = ".$result."\n"; + $this->assertContains('Bad string syntax to evaluate', $result); + + $result=dol_eval('`ls`', 1, 0); + print "result = ".$result."\n"; + $this->assertContains('Bad string syntax to evaluate', $result); + } }