VYPR
High severityNVD Advisory· Published Nov 8, 2021· Updated Aug 4, 2024

Evaluation of closures can lead to execution of methods & functions in current program scope

CVE-2021-41170

Description

neoan3-template before 1.1.1 allows direct closure injection, causing unintended execution of callable values from user input.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

neoan3-template before 1.1.1 allows direct closure injection, causing unintended execution of callable values from user input.

Vulnerability

Neoan3-apps/template (neoan3 minimal template engine) versions prior to 1.1.1 allow passing closures directly into the template engine. The vulnerability occurs when a value in the substitution array has the same name as a callable function or method in the current scope, causing unintended execution of that callable during template rendering. The issue was present in the embrace and related methods, affecting all users handling dynamic input [1][4].

Exploitation

An attacker needs the ability to supply data to the template engine, either through direct user input or by controlling database values. By providing a key named after an existing function or method (e.g., strtolower), the attacker can cause that function to be executed with the value as argument. A multi-step attack is plausible where the attacker first registers a closure via registerClosure or manipulates the substitution array to trigger unintended calls [1][4]. The commit patch shows that closures are no longer directly accepted [3].

Impact

Successful exploitation allows an attacker to execute arbitrary functions or methods in the context of the application. This can lead to information disclosure, code execution, or other malicious outcomes depending on the available functions and the application's environment. The attacker gains the same privileges as the template engine's runtime [1][4].

Mitigation

The vulnerability is fixed in version 1.1.1, released on 2021-11-08. Users should upgrade to this version or later. There are no workarounds other than hardcoding all template values, which defeats the purpose of a template engine. The patch disallows direct passing of closures in the substitution array, while still allowing registered closures via TemplateFunctions::registerClosure [3][4].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
neoan3-apps/templatePackagist
< 1.1.11.1.1

Affected products

2

Patches

1
4a2c9570f071

SECURITY: allowing for direct injection (Issue #8)

4 files changed · +12 15
  • composer.json+1 2 modified
    @@ -1,13 +1,12 @@
     {
         "name": "neoan3-apps/template",
         "description": "neoan3 minimal template engine",
    -    "version": "1.1.0",
    +    "version": "1.1.1",
         "license": "MIT",
         "autoload": {
             "psr-4": {
                 "Neoan3\\Apps\\": "./"
             }
    -
         },
         "require": {
             "ext-openssl": "*",
    
  • TemplateFunctions.php+4 4 modified
    @@ -103,7 +103,7 @@ private static function retrieveClosurePattern($pure, $closureName)
             if (!$pure) {
                 $pattern .= preg_quote(self::$registeredDelimiters[0]) . "\s*";
             }
    -        $pattern .= "$closureName\(([a-z0-9,\.\s]+)\)";
    +        $pattern .= "$closureName\(([a-z0-9,\.\s_]+)\)";
             if (!$pure) {
                 $pattern .= "\s*" . preg_quote(self::$registeredDelimiters[1]);
             }
    @@ -201,7 +201,6 @@ private static function evaluateTypedCondition(array $flatArray, $expression): b
             foreach ($flatArray as $key => $value) {
                 $pattern = '/' . $key . '([^.]|$)/';
                 if (preg_match($pattern, $expression, $matches)) {
    -
                     switch (gettype($flatArray[$key])) {
                         case 'boolean':
                             $expression = str_replace($key, $flatArray[$key] ? 'true' : 'false', $expression);
    @@ -241,11 +240,12 @@ static function nIf($content, $array)
                 return $content;
             }
     
    +        $array = Template::flattenArray($array);
    +        // important: first try closures
    +        $array = array_merge(self::$registeredClosures, $array);
             foreach ($hits as $hit) {
                 $expression = $hit->getAttribute('n-if');
    -            $array = Template::flattenArray($array);
                 $bool = self::evaluateTypedCondition($array, $expression);
    -
                 if (!$bool) {
                     $hit->parentNode->removeChild($hit);
                 } else {
    
  • Template.php+4 6 modified
    @@ -24,12 +24,10 @@ static function embrace($content, $array)
             $saveClosing = preg_quote(TemplateFunctions::getDelimiters()[1]);
             foreach ($flatArray as $flatKey => $value){
                 $flatKey = preg_replace('/[\/\.\\\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:\-]/', "\\\\$0",$flatKey);
    -            if(is_callable($value)){
    -                TemplateFunctions::registerClosure($flatKey,$value);
    -            } else {
    -                $content = preg_replace("/$saveOpening\s*$flatKey\s*$saveClosing/", $value, $content);
    -                $content = TemplateFunctions::tryClosures($flatArray, $content, false);
    -            }
    +            // PATCHED: direct function injection is not allowed anymore
    +            $content = preg_replace("/$saveOpening\s*$flatKey\s*$saveClosing/", $value, $content);
    +            $content = TemplateFunctions::tryClosures($flatArray, $content, false);
    +
             }
     
             return $content;
    
  • tests/TemplateTest.php+3 3 modified
    @@ -113,11 +113,11 @@ public function testEmbraceTypes()
         public function testCallback()
         {
             $array = [
    -            'myFunc' => function ($x) {
    -                return strtoupper($x);
    -            },
                 'some' => 'value'
             ];
    +        TemplateFunctions::registerClosure('myFunc',function($x){
    +            return strtoupper($x);
    +        });
             $t = Template::embraceFromFile('callback.html', $array);
     
             $this->assertStringContainsString('<p>VALUE</p>', $t);
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.