Flask框架安全性科普

前言

在本文中我们将讨论Flask框架中的安全特性以及一些潜在的问题,例如服务端模版注入,跨站脚本,HTML属性注入攻击。如果你还没有体验过Flask带来的乐趣,建议可以先用用。Flask是一个使用Python编写的强大的轻量级Web应用框架(毕竟是Python)。

谈谈注入

对于表示层,Flask利用Jinga2引擎,其使用方便,自动转义.html,htm,xml以及.xhtml文件中的内容。Flask允许在Python源代码中使用HTML字符串创建模版,Flask内部使用本地线程对象,这样就可以不用为了线程安全的缘故在同一个请求中在函数之间传递对象。

服务端模版注入

Flask框架中提供的模版引擎可能会被一些无量开发者利用引入一个服务端模版注入漏洞,如果对此感到有些困惑可以看看James Kettle在黑帽大会中分享的议题PDF,简而言之这个漏洞允许将语言/语法注入到模板中。在服务器的context中执行这个输入重现,根据应用的context可能导致任意远程代码执行(远端控制设备)

接下来为我们就看看使用模板字符串功能如何探索安全问题,思考下面的代码片段:

from flask import Flask, request, render_template_string, render_template

app = Flask(__name__)

@app.route('/hello-template-injection')
def hello_ssti():
 person = {'name':"world", 'secret':"UGhldmJoZj8gYWl2ZnZoei5wYnovcG5lcnJlZg=="}
 if request.args.get('name'):
 person['name'] = request.args.get('name')
 template = '''<h2>Hello %s!</h2>''' % person['name']
 return render_template_string(template, person=person)

####
# Private function if the user has local files.
###
def get_user_file(f_name):
 with open(f_name) as f:
 return f.readlines()

app.jinja_env.globals['get_user_file'] = get_user_file # Allows for use in Jinja2 templates

if __name__ == "__main__":
 app.run(debug=True)

然后浏览应用

ssti-init

挑一个我们来试试:

Ryan. {{person.secret}}

ssti-exec

代码已经把密码泄漏出来!,我们再试试另一个payload。最终得知这些隐私信息存储在tmp目录,get_user_file方法看起来就更有意思了,用它试试:

Ryan. {{ get_user_file("/tmp/secrets.txt") }}

ssti-lfi

通过模版进行本地文件包含(LFI)!由于使用字符串连接或替换出现这个问题,如果你是一位Flask开发者,你可能已经知道答案。Jinja2在模版中使用花括号{{}}包围环境变量,通过将我们的输出放置到这些括号内,可以阻止用户输入包含模版语法的数据在服务器的context中执行。

在修复后的适当位置尝试整行读取:

template = '<h2>Hello {{ person.name }}!</h2>'

ssti-escaped

这样做可以降低了服务端模板注入的威胁。

跨站脚本

如上所述,Flask对某些文件提供了一个自动转义的特性。虽然这个特性非常不错,但是我们还是要提醒你:

  1. 模版可以禁用该特性
  2. 模板字符串非公共文件扩展名默认情况下是不启用自动转义功能的

它如何保护我们免受XSS危害?

接下来我们尝试使用一个常见的XSS测试字符串:

ssti-xss

还记得我说过模版字符串不会自动转义么?现在可以理解了吧。为了修复这个问题,我们可以通过手工绕过输出过滤,加上|e就可以保证在过滤进行之前就反馈给用户。所以我们最终的模版字符串应该是这样的:

template = '<h2>Hello {{ person.name | e }}!</h2>'

并非所有的应用都在使用on-the-fly模版,那么更传统的跨站脚本攻击是在静态模版中?

看看下面的函数:

def hello_xss():
 name = "world"
 template = 'hello.unsafe' # 'unsafe' file extension... totally legit.
 if request.args.get('name'):
 name = request.args.get('name')
 return render_template(template, name=name)

需要注意的是Python代码在模版中调用render_template,这不是一个会自动转义的文件扩展。根据模版中的代码hello.unsafe,我们可能得到了一个跨站脚本漏洞,以下为模版代码:

{% autoescape true %} 
<h2>Good</h2><p>
 Hello {{ name }}! I don't trust your input. I escaped it, just in case.</p>{% endautoescape %}<h2>Bad</h2><p>
 I trust all data! How are you {{ name }}? 
</p>

让我们来测试一下:

xss-exec

自动转义模块和预期一般正常工作;我们对输出进行适当转义。然而,第二部分允许payload注入在浏览器中执行。

“Good”部分利用Jinga2引擎的自动转义功能,我们也可以利用|e过滤。以下为在”Bad”部分中使用|e过滤的输出:

xss-escaped

这就完了?不!

Flask中转移输出

转义函数对于防护HTML属性注入没啥卵用,以下面代码为例:

def hello_hi():
 template = '''<title>No Injection Allowed!</title>
 <a href={{ url_for('hello_xss')}}?name={{ name |e}}>
 Click here for a welcome message</a>'''
 name = "world"
 if request.args.get('name'):
 name = request.args.get('name')
 return render_template_string(template, name=name)

我们可以看到,变量周围都是{{}},并使用|e绕过输出过滤

hi-init

现在,将鼠标悬停在链接:

hi-exec

payload执行了。还有有一个问题:由于name参数出现在HTML属性的context,我们的payload成功执行。

其次,单/双引号中的context属性输出封装可以解决这个问题,以下为更新后的链接标签:

<a href='{{ url_for('hello_xss')}}?name={{ name |e}}'>...</a>

总结

我们已经看到了一些Flask中提供的特性进行输出过滤绕过,潜在的安全问题,以及如果你遇到这些情况该怎么修复。记住一定要对输出进行验证。

相关文章

 

*参考来源:nvisium,MottoIN小编翻译,转载请注明来自MottoIN!

原创文章,作者:Moto,如若转载,请注明出处:http://www.mottoin.com/article/web/94149.html

发表评论

登录后才能评论

联系我们

021-62666911

在线咨询:点击这里给我发消息

邮件:root@mottoin.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code