SSTI服务器模板注入

0x00 前言

前段时间打本校的ctf,有道叫幸运数字的题,只挖到一个xss,看了很久对题完全没有头绪,后来交wp的时候,出题师傅在群里说是SSTI,然后就去百度了一下,复现并学习一下这个漏洞。

0x01 SSTI服务端模板注入

服务端模板注入是服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。

通常测试模块类型的方式如下图:

0x02 环境配置

前面两篇正好写了python的后端模块flask和jinja2,这次我们使用flask+jinja2框架来测试一下。

首先后端是python,所以需要python环境,python在官网下载并安装,配置环境变量即可。

接着我们安装需要用到的库

  • pip install jinja2
  • pip install flask
  • pip install virtualenv

只要上述所有库成功安装,环境就配置完毕了。

0x03 漏洞复现

我们的环境需要一个run.py文件和一个templates文件夹,templates文件夹内要有一个index.html。

index.html内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %}test pages{% endblock %}</title>
</head>
<body>
<center>
<p>请输入你的昵称</p>
<form method="post" action="/check">
<p>
<input type="text" name="name" required>
</p>
<br>
<p>
<input type="submit" name="submit" value="Submit">
</p>
</form>
</center>
</body>
</html>

可以看到,html中只有一个post型的from需要我们填写提交。

然后是run.py(注意这次我用了python2.7):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#python2.7
import jinja2
from flask import Flask,render_template, render_template_string, flash, redirect, url_for, request
app = Flask(__name__)
app.config.from_object(__name__)
app.config['SECRET_KEY'] = "password:123456789"
@app.route('/')
def index():
return render_template('index.html')
@app.route('/check',methods=['POST', 'GET'])
def check():
if request.method == 'POST':
name = str(request.form['name'])
template = u'''
<center>
<p>你好, %s, 欢迎来到我的页面</p>
<a href="/">点这里退出</a>
</center>
''' % (name)
return render_template_string(template)
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'),404
if __name__ == "__main__":
app.run()

这里python文件实现了将几个页面通过jinja2模块渲染部署,然后添加了一个敏感数据SECRET_KEY一会用得到,剩下的就是将index.html页面post过来的name,在网站check路径的页面中作为字符串输出。

我们在run.py目录打开命令行先输入python run.py先将服务运行起来:

然后我们打开浏览器输入127.0.0.1:5000,成功显示index.html中的页面:

我们输入任意字符串,浏览器跳转到127.0.0.1:5000/check目录,并且在网页中输出我们的字符串:

如上几步如果都成功,我们漏洞环境就搭建完成了。

0x04 漏洞利用

我们先尝试一下刚才图中的测试数据,输入

1
{{7*'7'}}

发现结果如下:

config是Flask模版中的一个全局对象,它代表“当前配置对象(flask.config)”,它是一个类字典的对象,它包含了所有应用程序的配置值。在大多数情况下,它包含了比如数据库链接字符串,连接到第三方的凭证,SECRET_KEY等敏感值。查看这些配置项目,我们只需注入如下命令

1
{{ config.items() }}

发现结果中存在我们刚才故意放进去的敏感信息:

在接下来的步骤之前,我先介绍两个内省实用程序:mro以及subclasses属性。

mro中的MRO(Method Resolution Order)代表着解析方法调用的顺序,它是每个对象元类的一个隐藏属性,当进行内省时会忽略dir输出。

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

mro允许我们在当前Python环境中追溯对象继承树,之后subclasses又让我们回到原点。从一个new-style object开始,例如str类型。使用mro我们可以从继承树爬到根对象类,之后在Python环境中使用subclasses爬向每一个new-style object。ok,这让我们能够访问加载到当前Python环境下的所有类。这样,我们就可以实现很多功能。

首先我们要做的第一件事便是选择一个new-style object用于访问object基类。我们可以使用mro属性访问对象的继承类。
输入

1
{{ ''.__class__.__mro__ }}

得到反馈只有str、basestring和object,按照数组的排序,我们应该选mro的第三个,也就是mro[2],接下来我们构造

1
{{ ''.__class__.__mro__[2].__subclasses__() }}

可以查看到非常非常多的可访问类,这个时候我们要找到类,它是文件系统访问的关键。

然后我们可以用file类的read()方法访问一下当前目录的文本:

1
{{ ''.__class__.__mro__[2].__subclasses__()[40]('./2333.txt').read() }}

访问敏感目录可以得到很多信息:

1
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}

我们就可以参考目标系统目录结构,对目标系统敏感文件进行访问。

0x05 参考资料