打开网站,发现他给我们源码了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)

分析代码

首先设置config中FLAG的值为环境变量FLAG的值,这个应该是我们要获取的。

接着源代码里定义了两个路由,分别是’/‘根路由和’shrine’路由,根路由作用是读取__file__魔术变量文件的内容,没有我们可以利用的,所以直接看shrine路由。

这个路由接收一个参数,具体传参的形式为http://domain/shrine/参数,不是普通的POST或者GET传参。再往下看,定义了一个函数,接收参数为s,把s内的'('和')'过滤了。然后定义黑名单,用join函数连接格式化后的黑名单字符串。具体过程是这样的:

1
2
3
4
'{{% set {}=None%}}'.format(c) for c in blacklist    c=config
==>{{% set config=None%}}
'{{% set {}=None%}}'.format(c) for c in blacklist c=self
==>{{% set self=None%}}

最后用join函数把他们连接起来,连接符为前面的’’,即空,最后的字符串为:{{% set config=None%}}{{% set self=None%}},然后把最后的字符串与s连接返回。

接着往下看,这个路由返回flask.render_template_string(safe_jinja(shrine)),即把用户的输入和前面的{{% set config=None%}}{{% set self=None%}}连起来传给渲染函数。也就是说当我们进行ssti时,config和self这两个参数的值会是None。验证一下:


这也证明了上述分析代码是正确的。

payload

虽然直接获取config的方法被ban了,但是我们还是可以通过全局变量访问当前程序来获取config的。我们需要利用python里的get_flashed_messages()函数,这个函数是用于获取 Flask 消息闪现机制的信息,然后返回给用户的。一般是用来发个警告。不管如何,它接收一个参数,这个参数是可以返回给我们的。那么就可以构造payload:{{get_flashed_messages.__globals__}}

可以看到有current_app,那么直接访问它config中的FLAG即可。

最终payload:{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}

获取flag:flag{c689709f-823f-499e-9635-1911ff42ef4e}

更新于

请我喝[茶]~( ̄▽ ̄)~*

Nebu1ea 微信支付

微信支付

Nebu1ea 支付宝

支付宝

Nebu1ea 贝宝

贝宝