一.初识SSTI

1.什么是SSTI注入?

SSTI模板注入(Server-Side Template Injection),通过与服务端模板的输入输出交互,在过滤不严格的情况下,构造恶意输入数据,从而达到读取文件或者getshell的目的。

2.SSTI漏洞成因

​漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。

3. 利用条件

网站由数据与模板框架处理输出页面,我们的数据在数据库不会改变,但是画面的模板可以转换多样,当模板存在可控的参数变量或模板代码内有模板的调试功能,可能会导致SSTI模板注入,对大多数脚本类型均存在该注入。

二. 含注入风险的模板框架

1. python中的SSTI

python常见的模板有:Jinja2,tornado

Jinja2

python模板注入漏洞的产生在于Flask应用框架中render_template_string函数在渲染模板的时候使用了%s来动态的替换字符串,而且Flask模板中使用了Jinja2作为模板渲染引擎,{

{}}在Jinja2中作为变量包裹标识符,在渲染的时候将{

{}}包裹的内容作为变量解析替换,比如{

{1+1}}会被解析成2。

以一道ctf例题展示模板框架及利用方法:

攻防世界shrine wp

源码:

import flask

import os

app = flask.Flask(__name__)

#用当前模块的路径初始化app,__name__是系统变量即程序主模块或者包的名字,该变量指的是本py文件的文件名。

app.config['FLAG'] = os.environ.pop('FLAG')

#设置一个配置:app.config[‘FLAG’]就是当前app下一个变量名为’FLAG’的配置,

#它的值等于os.environ.pop(‘FLAG’)移除环境变量中的键名为’FLAG’的值。

#访问http://ip/,则执行index()函数打开当前文件,读取文件内容,返回文件源码

@app.route('/')

def index():

return open(__file__).read()

#访问http://ip/shrine/,则调用flask.render_template_string函数

#返回渲染模板字符串safe_jinja(shrine)

@app.route('/shrine/')

def shrine(shrine):

def safe_jinja(s):

s = s.replace('(', '').replace(')', '') #先去掉s字符串变量中的“(”和“)”左右括号

blacklist = ['config', 'self'] #过滤掉config, self 关键字

return ''.join(['{

{% set {}=None%}}'.format(c) for c in blacklist])+s

return flask.render_template_string(safe_jinja(shrine)) #渲染模板

if __name__ == '__main__':

app.run(debug=True)

shrine路径下,构造Python模板注入,发现存在模板注入

之后查看config、self配置:

过滤了括号和关键字,所以带括号的魔法函数都不能使用,可以利用其他Python内置函数

Python中常用于SSTI的魔术方法

__class__:返回类型所属的对象

__mro__:返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。

__base__:返回该对象所继承的基类

// __base__和__mro__都是用来寻找基类的

__subclasses__:每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表

__init__:类的初始化方法

__globals__:对包含函数全局变量的字典的引用

__builtins__:builtins即是引用,Python程序一旦启动,它就会在程序员所写的代码没有运行之前就已经被加载到内存中了,而对于builtins却不用导入,它在任何模块都直接可见,所以可以直接调用引用的模块。

app.config['FLAG'] = os.environ.pop('FLAG')

这道题中的目的是读取配置文件中变量名为’FLAG’的值,也就是环境变量中的键名为’FLAG’的值,但是config、self参数的值设为None,无法直接查看,可利用如下两种函数:

url_for()函数查看flag

用url_for函数来查看当前包中所有的静态文件,其中包括了配置文件。

先查看url_for函数的全局变量的字典的引用:

其中’current_app’: 键值对,current_app意思是当前app,那么我们直接查看app.config[‘FLAG’]

get_flashed_messages()函数查看flag 

flash()用于闪现(可以理解为发送)一个消息。在模板中,使用 get_flashed_messages() 来获取消息(闪现信息只能取出一次,取出后闪现信息会被清空)。

flash()函数有三种形式缓存数据: (1)缓存字符串内容。 设置闪现内容:flash(‘恭喜您登录成功’) 模板取出闪现内容:{% with messages = get_flashed_messages() %} (2)缓存默认键值对。当闪现一个消息时,是可以提供一个分类的。未指定分类时默认的分类为 ‘message’ 。 设置闪现内容:flash(‘恭喜您登录成功’,“status”) 模板取出闪现内容:{% with messages = get_flashed_messages(with_categories=true) %} (3)缓存自定义键值对。 设置闪现内容:flash(‘您的账户名为admin’,“username”) 模板取出闪现内容:{% with messages = get_flashed_messages(category_filter=[“username”])

所以我们可以通过get_flashed_messages()来获取所有缓存的闪现内容:

http://61.147.171.105:54585/shrine/{

{get_flashed_messages.__globals__}}

好文阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: