freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课
FreeBuf+小程序

FreeBuf+小程序

官方公众号企业安全新浪微博

国内领先的互联网安全新媒体,同时也是爱好者们交流与分享安全技术的社区

乱弹Flask注入 金币
2015-12-14 09:00:27

在本文中我们会乱弹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_templateapp = 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 templatesif __name__ == "__main__":
    app.run(debug=True)

接着浏览应用

非常好,虽然我不是一位大逗逼,但是我也起了一些不错的昵称,挑一个我们来试试:

Ryan. {{person.secret}}

凹不,我们的代码已经把密码泄漏出来!毫无疑问这是一件非常糟糕的事情。保险起见,我们再试试另一个payload。最终得知这些隐私信息存储在tmp目录,get_user_file方法看起来就更有趣了,尝试下:

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

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

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

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

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

跨站脚本

如上所述,Flask对某些文件提供了一个自动转义的特性。

虽然这个特性非常不错,但是我们还是要提醒你:

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

尝试使用一个常见的XSS测试字符串:

Ryan. <script>alert("Full protection not yet achieved.")</script>

还记得我说过模版字符串不会自动转义么?现在可以理解了吧。为了修复这个问题,我们可以通过手工绕过输出过滤

,加上|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>

测试下:

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

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

附注: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绕过输出过滤

现在,鼠标在链接周围移动

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

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

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

尾声

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

谨记一定要对输出进行验证!

*参考来源:nvisium,编译/FB小编鸢尾,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

本文作者:, 转载请注明来自FreeBuf.COM

# 远程代码执行 # Flask框架 # 模版注入
被以下专栏收录,发现更多精彩内容
+ 收入我的专栏
评论 按时间排序

登录/注册后在FreeBuf发布内容哦

相关推荐
  • 0 文章数
  • 0 评论数
  • 0 关注者
登录 / 注册后在FreeBuf发布内容哦