CVE-2021-21408: Improper Input Validation (CWE-20)
value: Smarty is a template engine for PHP, facilitating the separation of presentation (HTML/CSS) from application logic. Prior to versions 3.1.43 and 4.0.3, template authors could run restricted static php methods. Users should upgrade to version 3.1.43 or 4.0.3 to receive a patch.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 8.8
Published: 2022-01-10T20:15Z
Repository: https://github.com/smarty-php/smarty
Language: PHP
Files changed: CHANGELOG.md, lexer/smarty_internal_templateparser.y, libs/sysplugins/smarty_internal_templateparser.php, tests/UnitTests/SecurityTests/SecurityTest.php
Commit date: 2022-01-10 10:48:27 +0100
Commit message: Merge pull request from GHSA-4h9c-v5vg-5m6m

* Prevent evasion of the static_classes security policy.

* Updated deprecated exception expectations.
Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee8eed885..31df422e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,13 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
 
+### Security
+- Prevent evasion of the `static_classes` security policy. This addresses CVE-2021-21408
+
 ## [4.0.2] - 2022-01-10
 
 ### Security
 - Prevent arbitrary PHP code execution through maliciously crafted expression for the math function. This addresses CVE-2021-29454
 
diff --git a/lexer/smarty_internal_templateparser.y b/lexer/smarty_internal_templateparser.y
index 3786466ff..620498765 100644
--- a/lexer/smarty_internal_templateparser.y
+++ b/lexer/smarty_internal_templateparser.y
@@ -745,10 +745,13 @@ value(res)       ::= doublequoted_with_quotes(s). {
     res = s;
 }
 
 
 value(res)    ::= varindexed(vi) DOUBLECOLON static_class_access(r). {
+    if ($this->security && $this->security->static_classes !== array()) {
+        $this->compiler->trigger_template_error('dynamic static class not allowed by security setting');
+    }
     $prefixVar = $this->compiler->getNewPrefixVariable();
     if (vi['var'] === '\'smarty\'') {
         $this->compiler->appendPrefixCode("<?php {$prefixVar} = ". $this->compiler->compileTag('private_special_variable',array(),vi['smarty_internal_index']).';?>');
      } else {
         $this->compiler->appendPrefixCode("<?php  {$prefixVar} = ". $this->compiler->compileVariable(vi['var']).vi['smarty_internal_index'].';?>');
diff --git a/libs/sysplugins/smarty_internal_templateparser.php b/libs/sysplugins/smarty_internal_templateparser.php
index a065c94f7..a2dd0d6fb 100644
--- a/libs/sysplugins/smarty_internal_templateparser.php
+++ b/libs/sysplugins/smarty_internal_templateparser.php
@@ -2395,10 +2395,13 @@ public static $yy_action = array(
     public function yy_r90(){
       $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;
     }
 // line 749 "../smarty/lexer/smarty_internal_templateparser.y"
     public function yy_r94(){
+	    if ($this->security && $this->security->static_classes !== array()) {
+		    $this->compiler->trigger_template_error('dynamic static class not allowed by security setting');
+	    }
     $prefixVar = $this->compiler->getNewPrefixVariable();
     if ($this->yystack[$this->yyidx + -2]->minor['var'] === '\'smarty\'') {
         $this->compiler->appendPrefixCode("<?php {$prefixVar} = ". $this->compiler->compileTag('private_special_variable',array(),$this->yystack[$this->yyidx + -2]->minor['smarty_internal_index']).';?>');
      } else {
         $this->compiler->appendPrefixCode("<?php  {$prefixVar} = ". $this->compiler->compileVariable($this->yystack[$this->yyidx + -2]->minor['var']).$this->yystack[$this->yyidx + -2]->minor['smarty_internal_index'].';?>');
diff --git a/tests/UnitTests/SecurityTests/SecurityTest.php b/tests/UnitTests/SecurityTests/SecurityTest.php
index 72bb3cdc0..4d1b8a3ab 100644
--- a/tests/UnitTests/SecurityTests/SecurityTest.php
+++ b/tests/UnitTests/SecurityTests/SecurityTest.php
@@ -255,23 +255,45 @@ class SecurityTest extends PHPUnit_Smarty
         $this->smarty->security_policy->static_classes = array('mysecuritystaticclass');
         $tpl = $this->smarty->createTemplate('string:{mysecuritystaticclass::square(5)}');
         $this->assertEquals('25', $this->smarty->fetch($tpl));
     }
 
-/**
- * test not trusted PHP function
-  * @runInSeparateProcess
-  * @preserveGlobalState disabled
-  */
-    public function testNotTrustedStaticClass()
-    {
+	/**
+	 * test not trusted PHP function
+	 * @runInSeparateProcess
+	 * @preserveGlobalState disabled
+	 */
+	public function testNotTrustedStaticClass()
+	{
         $this->expectException('SmartyException');
         $this->expectExceptionMessage('access to static class \'mysecuritystaticclass\' not allowed by security setting');
         $this->smarty->security_policy->static_classes = array('null');
         $this->smarty->fetch('string:{mysecuritystaticclass::square(5)}');
     }
 
+	/**
+	 * test not trusted PHP function
+	 */
+	public function testNotTrustedStaticClassEval()
+	{
+		$this->expectException('SmartyException');
+		$this->expectExceptionMessage('dynamic static class not allowed by security setting');
+		$this->smarty->security_policy->static_classes = array('null');
+		$this->smarty->fetch('string:{$test = "mysecuritystaticclass"}{$test::square(5)}');
+	}
+
+	/**
+	 * test not trusted PHP function
+	 */
+	public function testNotTrustedStaticClassSmartyVar()
+	{
+		$this->expectException('SmartyException');
+		$this->expectExceptionMessage('dynamic static class not allowed by security setting');
+		$this->smarty->security_policy->static_classes = array('null');
+		$this->smarty->fetch('string:{$smarty.template_object::square(5)}');
+	}
+
     public function testChangedTrustedDirectory()
     {
         $this->smarty->security_policy->secure_dir = array(
             '.' . DIRECTORY_SEPARATOR . 'templates_2' . DIRECTORY_SEPARATOR,
         );

Vulnerable code: /**
 * test not trusted PHP function
  * @runInSeparateProcess
  * @preserveGlobalState disabled
  */
    public function testNotTrustedStaticClass()
    {
Fixed code: ### Security
- Prevent evasion of the `static_classes` security policy. This addresses CVE-2021-21408

    if ($this->security && $this->security->static_classes !== array()) {
        $this->compiler->trigger_template_error('dynamic static class not allowed by security setting');
    }
	    if ($this->security && $this->security->static_classes !== array()) {
		    $this->compiler->trigger_template_error('dynamic static class not allowed by security setting');
	    }
	/**
	 * test not trusted PHP function
	 * @runInSeparateProcess
	 * @preserveGlobalState disabled
	 */
	public function testNotTrustedStaticClass()
	{
	/**
	 * test not trusted PHP function
	 */
	public function testNotTrustedStaticClassEval()
	{
		$this->expectException('SmartyException');
		$this->expectExceptionMessage('dynamic static class not allowed by security setting');
		$this->smarty->security_policy->static_classes = array('null');
		$this->smarty->fetch('string:{$test = "mysecuritystaticclass"}{$test::square(5)}');
	}

	/**
	 * test not trusted PHP function
	 */
	public function testNotTrustedStaticClassSmartyVar()
	{
		$this->expectException('SmartyException');
		$this->expectExceptionMessage('dynamic static class not allowed by security setting');
		$this->smarty->security_policy->static_classes = array('null');
		$this->smarty->fetch('string:{$smarty.template_object::square(5)}');
	}