• 1. Circles - Post
  • 2. Hollywood's_Bleeding - Post
  • 3. A_Thousand_Bad_Times - Post
  • 4. Allergic - Post
  • 5. Happier - Marshmello
  • 6. Here_With_Me - Marshmello

ssti漏洞学习笔记

最近做题的时候碰到了ssti注入,去了解了一下,多数是存在与flask框架里或使用jinja2模块的web应用里,本事我是拒绝的,但是去学习了一下还是比较好懂的,这里对各位大佬的讲解,进行粗略的总结.

ssti粗浅理解

ssti模块注入,实际上还是注入类型的一种,其原因都是信任用户输入或者没有进行适当数据过滤,进而导致执行恶意代码的问题.这里ssti也是类似的,先来看个例子吧,这个例子取自vulhub里的flask一节

from flask import Flask, request
from jinja2 import Template

app = Flask(__name__)

@app.route("/")
def index():
    name = request.args.get('name', 'guest')

    t = Template("Hello " + name)
    return t.render()

if __name__ == "__main__":
    app.run()

这是一个非常简单的flaskweb应用,只是通过获得用户get进来的name参数,进行相应的显示,主要问题在于对用户输入的name参数是直接传给Template对象进行拼接的,没有进行过滤,随后利用render()方法进行渲染时,该方法会对用户输入进行解析,这就是ssti注入的成因了.
以下是简单示例:


这里可以看见,服务器已经将我们输入的数据进行了解析并运行了,因此这里存在注入的问题,那么问题来了,我们需要知道payload是怎么来的,根据官方给的payload为:

{% 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 %}

第一句payload

{% for c in [].__class__.__base__.__subclasses__() %}

__class__

先一步步来,我们将payload一行行拆开,了解每一行的作用到底是什么,首先我们需要知道在python中'.__class__'是用来获得当前数据实例化的类是什么的,举个例子:

我们可以看到,利用'.__class__'获得了对应数据所使用的类,因此payload第一句是获得'[]'对应的类为"<class 'list>'"

__base__及其其他

接着'.__bases__'和'.__mro__'类似,是获得该类的父类集合的元组,简单的例子如下:

注意__mro__和__bases__的区别,__mro__会将本类包含,而__bases__则不会包含本类,顺便说以下__base__和__bases__不太同,简单举例如下:

因此如果不是存在过滤的情况,可以直接使用__base__获得基本类(object),基本类(object)是所有类的父类

__subclasses__()

这个属性是用于查看类中所有存活子类的引用,可以理解为只要有子类应用该父类,其返回就会包含该子类,我们已经知道了object是所有类的父类,因此利用Object基类去调用该属性,会返回所有可调用的子类,如下图:

因此第一条语句是在用for循环进行遍历寻找可调用类中的子类.

第二条语句

{% if c.__name__ == 'catch_warnings' %}

__name__属性相信大家都不陌生了,这个属性常常用在

if__name__=='__main__'

作为语句开始,他的作用是获得当前调用模块的名字,因此这条语句的作用是判断当前使用模块的名字是否为"catch_warnings",同时我们需要知道是什么模块名字为"catch_warnings",是我们用于系统命令操作的os模块,因为访问os模块都是从warnings.catch_warnings入手的,这里就相当于我们在寻找os模块

第三条语句

{% for b in c.__init__.__globals__.values() %}

这个语句首先将查找到os模块进行实例化,然后利用__globals__查找该模块下一个包含可使用的方法和变量的字典,后面那个values()实际上是调用Python字典里面values()方法,将键值对的值取出并将这些值进行迭代,其值如下,都是方法或者变量

第四,五句语句

{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}

这两句先是查找了b中类与字典类相同的变量和函数(模块中的函数是以键值对的形式存储的,因此需要寻找同字典类型的类),然后再在对应b中寻找有无'eval'为Key值的存在,举个例子就知道这条语句在干什么了,例子如下:

这里我们看到已经找了eval函数的所在地了,现在需要做的只是使用它

最后一句语句

{{ b['eval']('__import__("os").popen("id").read()') }}

这里相当于调用了eval()方法执行我们需要注入的内容,然后利用__import__动态加载入了os模块,利用.popen()方法打开一条系统通道,在执行这条命令之后返回了一个file类型的对象,从而可以让我们读取执行命令之后的内容,随后利用file对象里的read方法进行读取,从而获得回显.

有关使用的问题

不多说了直接上图吧
这里使用Linux里的id命令:

类似的还可以执行ls命令:

这就已经可以拿到webshell了

有关另一种payload

这种payload思考方法和前面的类似,都是通过基类object进行查找,找到os所在的位置然后执行相应的系统命令,同类型的payload有:

[].__class__.base__.__subclasses__()[(warnings.catch_warnings所在位置)].__init__.func_globals.linecache.os.popen('id').read()

这里利用了一个.func_globals属性是用于查找该类下的全局函数,后面的.linecache则是读取任意文件的某一行,通过这个方法找到os,然后再利用popen()执行系统命令,并用read进行读取.
更多信息可以参考以下文章:
FLASK模板注入 (SSTI)
探索Flask/Jinja2中的服务端模版注入(二)
用python继承链搞事情
------------------the end----------------------


除非注明,ebounce文章均为原创,转载请以链接形式标明本文地址

本文地址:http://www.ebounce.cn/web/19.html

新评论

captcha
请输入验证码