CVE-2022-21648: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Latte is an open source template engine for PHP. Versions since 2.8.0 Latte has included a template sandbox and in affected versions it has been found that a sandbox escape exists allowing for injection into web pages generated from Latte. This may lead to XSS attacks. The issue is fixed in the versions 2.8.8, 2.9.6 and 2.10.8. Users unable to upgrade should not accept template input from untrusted sources.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2022-01-04T20:15Z
Repository: https://github.com/nette/latte
Language: PHP
Files changed: src/Latte/Compiler/PhpWriter.php, tests/Latte/Policy.violations.phpt
Commit date: 2022-01-04 15:13:05 +0100
Commit message: PhpWriter: complex expression in strings prohibited in sandbox mode

Diff: diff --git a/src/Latte/Compiler/PhpWriter.php b/src/Latte/Compiler/PhpWriter.php
index 0e9a1034..83b575f2 100644
--- a/src/Latte/Compiler/PhpWriter.php
+++ b/src/Latte/Compiler/PhpWriter.php
@@ -224,10 +224,18 @@ public function validateTokens(MacroTokens $tokens): void
 				throw new CompileException('Unexpected ' . $tokenValue);
 
 			} elseif ($tokens->isCurrent('`')) {
 				throw new CompileException('Backtick operator is forbidden in Latte.');
 
+			} elseif (
+				$this->policy
+				&& $tokens->isCurrent($tokens::T_STRING)
+				&& $tokenValue[0] === '"'
+				&& (strpos($tokenValue, '{$') !== false || strpos($tokenValue, '${') !== false)
+			) {
+				throw new CompileException('Forbidden complex expressions in strings.');
+
 			} elseif (
 				Helpers::startsWith($tokenValue, '$ʟ_')
 				|| ($this->policy && $tokens->isCurrent('$this'))
 			) {
 				throw new CompileException("Forbidden variable {$tokenValue}.");
diff --git a/tests/Latte/Policy.violations.phpt b/tests/Latte/Policy.violations.phpt
index 6f262a12..71787737 100644
--- a/tests/Latte/Policy.violations.phpt
+++ b/tests/Latte/Policy.violations.phpt
@@ -126,5 +126,17 @@ public function __call($nm, $arg)
 }, Latte\CompileException::class, "Forbidden keyword 'yield' inside tag.");
 
 Assert::exception(function () use ($latte) {
 	$latte->compile('{do new stdClass}');
 }, Latte\CompileException::class, "Forbidden keyword 'new' inside tag.");
+
+Assert::exception(function () use ($latte) {
+	$latte->compile('{="{$var}"}');
+}, Latte\CompileException::class, 'Forbidden complex expressions in strings.');
+
+Assert::exception(function () use ($latte) {
+	$latte->compile('{="${var}"}');
+}, Latte\CompileException::class, 'Forbidden complex expressions in strings.');
+
+Assert::noError(function () use ($latte) {
+	$latte->compile('{=\'${var}\'}');
+});

Fixed code: 			} elseif (
				$this->policy
				&& $tokens->isCurrent($tokens::T_STRING)
				&& $tokenValue[0] === '"'
				&& (strpos($tokenValue, '{$') !== false || strpos($tokenValue, '${') !== false)
			) {
				throw new CompileException('Forbidden complex expressions in strings.');


Assert::exception(function () use ($latte) {
	$latte->compile('{="{$var}"}');
}, Latte\CompileException::class, 'Forbidden complex expressions in strings.');

Assert::exception(function () use ($latte) {
	$latte->compile('{="${var}"}');
}, Latte\CompileException::class, 'Forbidden complex expressions in strings.');

Assert::noError(function () use ($latte) {
	$latte->compile('{=\'${var}\'}');
});