服务端模板注入攻击 SSTI
1. SSTI模板注入(Server-side template injection)
a. 服务器模板注入是当攻击者能够用本地的模板语法去注入一个恶意的payload,然后再服务器端执行改模板的攻击手法。
b. 模板引擎使用过将固定模板与多边数据结合起来生成的html网页的一种技术,当用户直接输入数据到模板不做任何过滤额时,可能会发生服务端的模板注入攻击,这使得攻击者可以注入任何模板指令来操作服务器模板引擎,从而使整个服务器被控制。
顾名思义,服务器模板注入和SQL注入大同小异,但SSTI是发生在服务器端的,更加危险。
c. 服务器模板注入的危害性往往取决于所使用的模板引擎以及应用程序的使用方式,很少有情况不会带来真正的安全风险,但绝大多数情况下都是致命性的影响。最严重的的情况是攻击者可以进行RCE(远程代码执行),完全控制服务器端,并进行内网攻击;即使在无法完全进行RCE的情况下,攻击者也能将SSTI和其他漏洞进行“组合拳”攻击,比如可能读取服务器上的敏感文件等。
2. 为什么需要服务器模板
a. 简单来说,页面上的数据需要不断更新,即为渲染。后台语言通过一些模板引擎生成HTML的过程。(常见的模板渲染引擎Jade,YAML)
b. 由于WebApplication最终都是要落实到HTMl、CSS、JavaScript等用户界面上的,有一种情况是,每一个页面都需要特殊的逻辑,随着应用功能的增加,而彼此之间没有同步。比如你更改了站点的布局风格,那么响应的就需要修改成百上千的HTMl文件,显然是十分复杂的。
c. 既然如此多的HTML具有一定的逻辑联系,何不使用代码生成代码。于是后端模板语言诞生了。分别有前端渲染和后端(服务器)渲染,区别是后端渲染是将一些模板规范语言翻译成如上三种语言回传给前端;而前端渲染则是将整个生成逻辑代码全部回传前端,再由客户端生成用户界面。
Eg:
前端
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<form method="{{method}}" action="{{action}}">
<input type="text" name="user" value="{{username}}">
<input type="password" name="pass" value="">
<button type="submit">submit</button>
</form>
<p>Used {{mikrotime(true) - time}}</p>
</body>
</html>
后端
$template Engine=new TempLate Engine() ;
$template=$template Engine-load File('login.tpl') ;
$template->assign('title', 'login') ;
$template->assign('method', 'post') ;
$template->assign('action', 'login.php') ;
$template->assign('username', get Username From Cookie() ) ;
$template->assign('time', microtime(true) ) ;
$template->show() ;
首先加载login.tpl模板文件,然后对与模板中名称相同的变量赋值(大括号里的变量),然后调用show()函数,相应的替换它们
相应的HTML代码。
3. 漏洞原理
当用户直接输入数据到模板不做任何过滤,可能会发生服务端模板注入攻击。使用PHP模板引擎Twing作为例子来说明,末班注入产生的原理。
<?php ………… $output = $twig->render("Hello {{name}}", array("name" => $_GET["name"])); // 将用户输入作为模版变量的值 echo $output; ?>
使用Twing模板引擎渲染页面,其中模板含有{{name}}变量,其模板变量值来自于GET请求参数,$_GET['name']。显然这段代码并没有什么问题,其实你想通过name参数传递一段JavaScript代码给服务端进行渲染,但是由于模板引擎一般都默认对渲染的变量值进行编码和转义,所以并不会造成XSS
但是如果渲染的模板内容是用户可以控制的,情况就不一样了。修改代码为:
<?php
…………
$output = $twig->render("Hello {$_GET['name']}"); // 将用户输入作为模版内容的一部分
echo $output;
?>
上面这段代码带构建模板时,拼接了用户输入作为模板的内容,现在如果传入一段JavaScript代码,就会产生XSS问题。
4. 模板注入的检测
a. 寻找SSTI漏洞需要代码审计或者直接黑盒引发报错,最简单的方法是通过注入模板表达式中常用的特殊字符来Fuzz,例如:$ {{<%[%'“}}%\。如果引发了报错,则表明服务器模板可能存在漏洞。SSTI漏洞通常发生在两个不同的上下文中,所以要根据特定的上下文来进行Fuzz。
b. 例如,如下就是一个有漏洞的模板
<?php
$output = $twig->render("Hello {$_GET['name']}");
echo $output;
?>
在Twing模板引擎里,{{var}}除了可以输出传递的变量意外,还能执行一些基本的表达式然后将其结果作为改模板变量的值
例如
i. 这里输入name={{2*10}} http://127.0.0.1/ssti.php?name={{2*10}}
ii. 则服务端拼接模板的的内容为Hello {{2*10}};
iii. Twing模板引擎在编译模板的过程中会计算{{2*10}}中的表达式2*10,会将其返回值 20 作为模板的变量值输出,前端最终的返回内容为Hello 20
c.
i. 现在把测试的数据改变一下,插入一些正常字符和 Twig 模板引擎默认的注释符,构造 Payload 为:
IsVuln{# comment #}{{2*8}}OK
ii. 实际服务端要进行编译的模板就被构造为:
Hello IsVuln{# comment #}{{2*8}}OK
iii. 这里简单分析一下,由于 {# comment #} 作为 Twig 模板引擎的默认注释形式,所以在前端输出的时候并不会显示,而 {{2*8}} 作为模板变量最终会返回 16 作为其值进行显示,因此前端最终会返回内容 Hello IsVuln16OK
d. 通过上面连个简单的示例,可以得到SSTI扫描检测的大致流程(以Twing为例)
同常规的 SQL 注入检测,XSS 检测一样,模板注入漏洞的检测也是向传递的参数中承载特定 Payload 并根据返回的内容来进行判断的。每一个模板引擎都有着自己的语法,Payload 的构造需要针对各类模板引擎制定其不同的扫描规则,就如同 SQL 注入中有着不同的数据库类型一样。
简单来说,就是更改请求参数使之承载含有模板引擎语法的 Payload,通过页面渲染返回的内容检测承载的 Payload 是否有得到编译解析,有解析则可以判定含有 Payload 对应模板引擎注入,否则不存在 SSTI。