Flask Session伪造漏洞深度解析:从密钥泄露到身份劫持实战 1. 项目概述一次经典的Flask Session伪造实战最近在复盘一些经典的CTF Web题目BUUCTF平台上的这道“[HCTF 2018]admin 1”给我留下了很深的印象。它不像那些需要复杂链式利用的漏洞而是精准地指向了Flask框架一个非常典型且在实际开发中容易被忽视的安全问题——Session伪造。对于刚接触Web安全或者Flask开发的朋友来说这个案例就像一份绝佳的教学样本它清晰地展示了从信息泄露到密钥获取再到最终伪造身份的全过程。如果你对Flask的工作原理、Session机制或者“为什么我知道密钥就能成为任何人”感到好奇那么跟着我一起拆解这道题你不仅能拿到Flag更能透彻理解背后的安全逻辑。这道题的核心目标很明确以管理员admin身份登录系统并获取Flag。题目环境是一个典型的Flask Web应用提供了注册和登录功能。表面上看我们只是一个普通用户但通过一系列测试和分析我们发现整个安全大厦的基石——SECRET_KEY——暴露了。这就好比你知道了一把万能钥匙的铸造方法可以为自己打造任何房间的钥匙。接下来我会带你一步步重现我是如何找到这把“钥匙”并最终打造出“管理员身份钥匙”的。2. 核心漏洞原理Flask Session机制深度拆解要发起有效的攻击你必须先彻底理解攻击的对象。Flask的Session机制是其安全性的一个双刃剑它设计得非常巧妙但一旦配置不当就会成为最脆弱的环节。2.1 为什么HTTP需要SessionHTTP协议本身是“无状态”的服务器不会记得上一次是谁、做了什么。这对于需要登录、购物车等功能的现代网站来说是灾难。Session机制就是为了解决这个问题而生的。简单来说服务器为每个用户会话创建一个唯一的“储物柜”Session里面存放用户的状态信息如登录状态、用户名。为了把“储物柜钥匙”交给用户服务器将其放在响应头的Set-Cookie字段中通常这个Cookie的名字就是session。用户下次请求时浏览器会自动带上这把“钥匙”Cookie服务器就能找到对应的“储物柜”认出用户。2.2 Flask Session的独特之处客户端存储大多数框架如Django、Java Spring默认将Session数据存储在服务端数据库、Redis等客户端只保存一个随机生成的Session ID。但Flask采用了另一种思路它将序列化、签名后的整个Session数据直接存储在客户端的Cookie中。这种方式被称为“客户端Session”或“基于Cookie的Session”。它的工作流程是这样的序列化与压缩当你在Flask中设置session[‘username’] ‘guest’Flask会先将这个字典对象通过JSON序列化成字符串。然后为了减少Cookie体积它会使用zlib库进行压缩如果数据足够大。编码压缩后的二进制数据会经过Base64编码转换成可以在HTTP头部安全传输的字符串。生成时间戳Flask会生成一个当前时间的时间戳用于会话过期判断默认31天。计算签名最关键的一步Flask将上一步得到的“编码后的Session数据”和“时间戳”拼接起来然后使用应用配置的SECRET_KEY作为密钥通过HMAC密钥散列消息认证码算法生成一个密码学签名。组装Cookie值最终Flask会将这些部分用点号.连接起来格式为{base64编码的session数据}.{时间戳}.{HMAC签名}并将其设置为名为session的Cookie。一个真实的Flask Session Cookie看起来是这样的eyJ1c2VybmFtZSI6Imd1ZXN0In0.Y48ncA.H99Th2w4FzzphEX8qAeiSPuUF_0。你可以用点号将其拆分为三段。2.3 安全性的核心签名与SECRET_KEY为什么说这种机制是安全的关键在于签名。服务器在接收到Cookie后会做以下验证用同样的方法使用SECRET_KEY对收到的“Session数据”和“时间戳”重新计算一次HMAC签名。将计算出的新签名与Cookie中附带的签名进行比对。如果两者一致证明数据在传输过程中未被篡改且确实是由知道SECRET_KEY的合法服务器签发的。如果不一致Flask会直接丢弃这个Session视为无效。这里就引出了最致命的攻击面整个安全模型完全依赖于SECRET_KEY的保密性。如果攻击者通过某种方式如源码泄露、配置错误、任意文件读取获取了SECRET_KEY那么整个签名机制就形同虚设。攻击者可以解密任意用户的Session Cookie查看其内容。篡改Session数据例如将username从guest改为admin。使用正确的SECRET_KEY为篡改后的数据生成新的、有效的签名。将伪造的Cookie发送给服务器服务器验证签名通过从而信任其中伪造的身份信息。这就是“Flask Session伪造”攻击的完整逻辑。它本质上是一种“密钥泄露导致身份验证体系被绕过”的漏洞。注意很多人会混淆“解密”和“验证”。Flask的Session数据第一段仅是Base64编码本质上相当于明文没有SECRET_KEY也能解码查看内容。SECRET_KEY的作用是签名和验证防止数据被篡改。获取SECRET_KEY后我们不是去“解密”被加密的数据而是去“签署”我们任意构造的数据。3. 实战演练[HCTF 2018]admin 1 解题全流程理解了原理我们进入实战。我会以第一视角带你完整复现解题的每一步操作和思考过程。3.1 环境初探与信息收集首先访问题目提供的Web地址。一个常见的登录/注册界面。作为标准流程我注册了一个普通账号例如test/test123并登录。登录成功后使用浏览器开发者工具F12查看网络请求。在任何一个请求的Cookie栏里果然看到了一个名为session的Cookie其值正是我们刚才分析的“点分三段式”结构。这是第一个重要信息这是一个使用默认客户端Session的Flask应用。接下来是信息收集的关键。我习惯性地查看网页源代码。在注释中发现了宝藏!-- https://github.com/woadsl1234/hctf_flask/ --开发者或出题人无意或有意地将源码仓库地址留在了注释里。这通常是CTF题目的重要突破口。访问这个Github仓库或题目可能直接提供了源码文件我们成功下载到了应用的源代码。3.2 源码审计与密钥定位拿到源码后开始快速审计。项目结构通常包含主应用文件如app.py、main.py和配置文件。首先查看配置文件在Flask项目中SECRET_KEY通常定义在配置文件里如config.py、settings.py或者直接写在主应用文件中。我们很快找到了config.pySECRET_KEY ‘ckj123’Bingo我们毫不费力地拿到了整个应用安全的核心——SECRET_KEY。在实际渗透测试或漏洞挖掘中密钥可能通过其他方式泄露比如版本控制文件.git泄露从中可以找到历史版本中的配置文件。服务器错误信息回显有时会暴露部分配置。备份文件如config.py.bak,app.py~泄露。通过其他漏洞如本题可能存在的任意文件读取读取配置文件。分析主应用逻辑查看app.py或routes.py寻找身份验证和Flag相关的路由。from flask import Flask, session, render_template, request, redirect, url_for import config app Flask(__name__) app.config.from_object(‘config’) # 加载配置SECRET_KEY在此生效 app.route(‘/’) def index(): if session and session.get(‘name’) ‘admin’: return render_template(‘index.html’, flagflag) # 假设flag在这里 return render_template(‘index.html’)代码逻辑非常清晰如果Session存在且其中name字段的值为admin则渲染包含Flag的页面。我们的目标就是伪造一个nameadmin的Session。3.3 Session解密与内容分析在伪造之前我们先看看自己当前登录的普通用户Session里有什么。使用Python脚本或在线工具对Cookie进行解码因为数据部分只是Base64编码无需密钥。我编写了一个简单的解码脚本import base64 import json import zlib def decode_flask_session(cookie_value): # 分割Cookie值 data_b64, timestamp, signature cookie_value.split(‘.’) # 对数据部分进行Base64解码 data base64.urlsafe_b64decode(data_b64 ‘’ * (4 - len(data_b64) % 4)) # 尝试解压Flask会对较长数据压缩 try: data zlib.decompress(data) except: pass # 未压缩则跳过 # 将字节串转换为字符串并JSON解析 session_dict json.loads(data.decode(‘utf-8’)) return session_dict, timestamp, signature # 替换为你抓取的session cookie值 cookie ‘eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.Y48ncA.H99Th2w4FzzphEX8qAeiSPuUF_0’ session_data, ts, sig decode_flask_session(cookie) print(“Session Data:”, session_data) print(“Timestamp:”, ts) print(“Signature:”, sig)运行后输出可能类似于Session Data: {‘_fresh’: True, ‘_id’: ‘...’, ‘name’: ‘test’, ‘user_id’: ‘2’} Timestamp: Y48ncA Signature: H99Th2w4FzzphEX8qAeiSPuUF_0可以看到当前Session中name字段是testuser_id是2。我们的目标是将name修改为admin。3.4 使用工具伪造Session手动构造签名比较麻烦我们可以利用现成的工具。最常用的是flask-unsign它是一个强大的Flask Session操作命令行工具。首先安装pip install flask-unsign然后按照以下步骤操作使用获取到的SECRET_KEY破解签名验证密钥正确性flask-unsign --decode --cookie ‘你的session cookie值’如果密钥正确这条命令会直接解码出Session内容。但更常用的验证方式是使用--unsign尝试剥离签名并验证flask-unsign --unsign --cookie ‘你的session cookie值’ --secret ‘ckj123’如果密钥正确它会输出解码后的Session数据。伪造新的Session 我们需要构造一个字典包含我们想要的字段。根据源码我们需要nameadmin。可能还需要保留一些Flask内部字段如_fresh。flask-unsign --sign --secret ‘ckj123’ --cookie “{‘name’: ‘admin’}”这条命令会使用密钥ckj123对字典{‘name’: ‘admin’}进行序列化、编码、签名并输出完整的、伪造好的Session Cookie字符串。实操心得flask-unsign在签名时可能会自动添加一些内部字段如_fresh。为了完全控制你可以先解码一个合法Session将其保存为文件修改name字段后再用这个文件作为输入进行签名。命令如下# 1. 解码并保存到文件 flask-unsign --decode --cookie ‘原cookie’ session_data.json # 2. 编辑session_data.json文件将name改为admin # 3. 从文件读取并签名 flask-unsign --sign --secret ‘ckj123’ --cookie “$(cat session_data.json)”3.5 替换Cookie与获取Flag拿到伪造的Cookie字符串后最后一步就是让浏览器使用它。有多种方法浏览器开发者工具打开F12进入“应用程序”(Application)或“存储”(Storage)标签页找到Cookies选中当前网站域名将sessionCookie的值替换为伪造的值。使用浏览器插件如EditThisCookie可以更方便地编辑Cookie。命令行工具如curl在请求头中直接指定Cookie。curl -H “Cookie: session伪造的cookie值” http://题目地址/替换完成后直接刷新页面。如果一切正确页面应该会显示Flag或者跳转到管理员界面。在本题目中刷新首页后原本普通用户的界面发生了变化直接显示了最终的Flag。4. 漏洞的深入利用与拓展场景通过这道题我们掌握了最基本的“密钥泄露-伪造Session”的攻击路径。但在更复杂的环境下攻击方式会更加多样。4.1 密钥的获取方式不止一种除了源码泄露SECRET_KEY还可能通过以下方式被攻击者获取弱密钥爆破如果开发人员设置了非常简单的SECRET_KEY如‘123456’,‘flask’,‘secret’攻击者可以使用flask-unsign的爆破模式进行尝试。flask-unsign --unsign --cookie ‘目标cookie’ --wordlist /path/to/wordlist.txt其他漏洞链式利用例如题目[CISCN2019 华东南赛区]Web41就展示了另一种经典场景。应用存在任意文件读取漏洞通过读取/proc/self/cmdline发现是Python进程进而读取app.py源码。源码显示SECRET_KEY由随机数生成但随机数种子uuid.getnode()是服务器的MAC地址。攻击者再利用文件读取漏洞获取MAC地址文件/sys/class/net/eth0/address从而在本地重现随机数生成过程计算出完全相同的SECRET_KEY。这是一种“信息泄露逻辑推理”的组合拳。环境变量泄露在生产环境中SECRET_KEY通常通过环境变量设置。如果应用存在导致环境变量打印的错误处理如debugTrue时未捕获的异常或者通过某些接口如/debug、/status泄露了环境信息密钥也可能暴露。4.2 Session的安全加固措施作为开发者如何避免自己的Flask应用出现此类问题使用强SECRET_KEY必须使用足够长且随机的字符串作为密钥可以通过os.urandom(24)生成。绝对禁止使用硬编码的简单字符串。区分环境配置开发、测试、生产环境使用不同的SECRET_KEY。切勿将生产环境的密钥提交到代码仓库。使用服务器端Session对于安全性要求高的应用考虑使用Flask-Session等扩展将Session数据存储在服务端的Redis、Memcached或数据库中。客户端只保存一个无意义的Session ID。这样即使攻击者知道SECRET_KEY此时用于签名Session ID也无法直接篡改Session内容因为他们无法接触到存储在服务端的实际数据。关闭Debug模式生产环境务必设置app.debug False。Debug模式会暴露堆栈跟踪信息可能泄露源码片段、内部路径等敏感信息。设置Session过期时间合理配置PERMANENT_SESSION_LIFETIME缩短Session的有效期。对敏感操作进行重认证即使Session有效在进行密码修改、支付等关键操作时应要求用户再次输入密码或进行二次验证。4.3 针对加固场景的攻击思考即使应用采用了服务端Session如果攻击者获得了SECRET_KEY仍然可以伪造一个有效的Session ID从而“冒领”一个服务端的Session存储位置。虽然他们无法直接修改该位置的内容除非同时存在服务端存储的写漏洞但他们可以抢占一个合法用户的会话。因此保护SECRET_KEY永远是第一要务。此外还要注意Session Fixation会话固定攻击。即攻击者先获取一个有效的Session IDCookie诱骗受害者使用这个ID登录。登录后该Session ID就关联了受害者的权限攻击者再用这个ID就能以受害者身份访问系统。防御方法是在用户登录成功后务必使用session.regenerate()或重新分配一个新的Session ID。5. 常见问题与排查技巧实录在实战和教学过程中我总结了一些新手容易踩坑的地方和排查技巧。5.1 伪造失败的可能原因及排查问题现象可能原因排查步骤替换Cookie后Session立即失效被重置或跳转到登录页。1.SECRET_KEY错误这是最常见的原因。密钥与服务器使用的不匹配。2.Session数据结构不完整Flask可能依赖一些内部键如_id,_fresh,csrf_token。伪造时只提供了业务字段缺少这些内部字段导致服务器处理异常。1. 再次确认密钥来源的准确性。尝试用该密钥解码一个已知有效的Cookie看是否能成功。2. 解码一个有效的合法Session观察其完整结构。在伪造时复制整个结构只修改目标字段如name。使用flask-unsign从文件签名可以很好地保留结构。签名验证通过但权限依然不够。1.目标字段名错误服务器检查的是username而你修改的是name或者大小写不一致。2.存在额外的权限校验除了Session服务器可能还检查IP、User-Agent或与数据库中的状态进行二次校验。1. 仔细审计源码确认服务器检查的确切Session键名。2. 查看其他路由或中间件是否存在全局的权限检查逻辑。使用flask-unsign时报解码或签名错误。1.Cookie格式错误可能包含了引号或换行符。2.Python版本问题加解密库在不同Python版本间有细微差异。3.数据压缩问题工具在处理zlib压缩时可能出错。1. 确保复制的Cookie值完整且没有多余空格。2. 尽量使用与目标服务器相同的主要Python版本如Py2 vs Py3进行操作。对于CTF题目出题环境通常是Python2或Python3这会影响随机数等行为。3. 尝试使用--no-compress选项禁用压缩或者手动编写脚本进行更精细的控制。5.2 工具使用技巧与脚本编写flask-unsign进阶用法暴力破解除了使用--wordlist还可以指定字符集和长度进行暴力破解flask-unsign --unsign --cookie ‘...’ --brute --charset ‘abcdef123456’ --length 6。但这仅在密钥极短时可行。指定编码器如果应用使用了自定义的序列化方法非JSON可能需要指定--signer或编写自定义脚本。手工编解码脚本的意义虽然工具有效但理解并能手写编解码脚本至关重要。这能帮助你在工具不适用如字段顺序、编码有特殊处理时进行调试。核心就是模拟Flask的itsdangerous库的URLSafeTimedSerializer的工作流程。5.3 实战中的思维延伸遇到Flask题目可以养成以下检查习惯查看Cookie首先看Cookie名是否为session值是否为三段式。这是最快速的判断。寻找SECRET_KEY检查源码、备份文件、.git目录、环境变量、错误信息。检查序列化方式Flask默认使用JSON序列化。但如果有pickle序列化的迹象如题目涉及pickle.loads那可能是更危险的反序列化漏洞可能导致任意代码执行。注意时间戳Flask的Session有过期时间。如果伪造的Session时间戳与服务器时间相差太大默认超过31天可能会被拒绝。在伪造时可以生成一个当前的时间戳。最后我想强调的是Flask Session伪造漏洞的原理非常清晰是学习Web安全中“身份验证绕过”和“密钥管理”的绝佳案例。它告诉我们再坚固的密码学机制如果密钥保管不当一切皆是空谈。对于开发者要时刻牢记安全配置的重要性对于安全研究者则要培养敏锐的信息收集和逻辑串联能力往往Flag就藏在那些看似不起眼的注释、配置文件或者错误信息里。这道题的价值远不止于拿到一个Flag更在于它揭示了一个普遍的安全哲学安全是一个链条最薄弱的一环决定了它的强度。