[NSSRound#13 Basic] Flask Session Cookie 伪造
记录 Flask 客户端 Session 的识别、解码、SECRET_KEY 泄露以及使用 flask-unsign 重新签名的过程。
本文记录授权 CTF 靶场中的解题过程,仅用于学习 Flask Session 的签名与验证机制。
一、考察核心
- Flask Client-Side Session 机制
- 敏感信息泄露
- Cookie 重新签名与身份校验绕过
题目名称是 [NSSRound#13 Basic] flask?jwt?,但这里的 Token 实际上并不是标准 JWT,而是 Flask 的客户端 Session Cookie。
二、解题流程与关键步骤
1. 协议识别
通过响应头中的服务器信息:
Server: Werkzeug/2.0.3 Python/3.10.6
再结合 Cookie 的格式进行判断。以 .eJw... 开头通常说明 Flask Session 的数据经过了压缩;这是一种常见特征,但并非所有 Flask Session 都一定以该前缀开头。
Flask 默认客户端 Session 通常是签名而非加密:客户端能够解码并查看其中的数据,但在不知道 SECRET_KEY 的情况下,正常来说无法生成服务器能够接受的新签名。
2. 数据解码
使用 flask-unsign 解码 Cookie:
flask-unsign --decode --cookie ".eyJ..."
参数说明:
--decode:只解码 Session 内容,不验证或破解签名。--cookie:指定完整的 Flask Session Cookie 值,不需要带上session=。
解码后发现 Session 中的身份标识为:
"_user_id": "2"
由此确认需要进一步验证将 _user_id 修改为 1 后,是否对应管理员身份。
3. 获取密钥:敏感信息泄露
在重置密码或忘记密码接口中,通过源码泄露直接获得后端的 SECRET_KEY:
th3f1askisfunny
因此本题不需要再通过字典猜测密钥。真正的漏洞链是:源码泄露导致 SECRET_KEY 暴露,随后攻击者能够修改 Session 内容并生成有效签名。
4. 重新签名 Session
将 Payload 中的 _user_id 改为 1,再使用泄露的 SECRET_KEY 生成新的 Session Cookie:
flask-unsign --sign -c '{"_user_id":"1"}' --secret "th3f1askisfunny"
参数说明:
--sign:使用给定密钥为 Session 内容生成合法签名。-c:指定需要写入 Session 的字典内容。--secret:指定 Flask 应用使用的SECRET_KEY。
如果解码结果还包含 _fresh、_id 等 Flask-Login 字段,应用可能会依赖这些字段判断登录状态。此时应在原始解码结果的基础上只修改 _user_id,保留其余必要字段。下面是本题使用完整 Payload 重新签名时的结果:

5. 获取 Flag
将生成的新值替换到访问 /getflag 时发送的 Cookie 中:
Cookie: session=<重新签名后的 Cookie>
发送请求后,成功以管理员身份通过鉴权并获取 Flag。
三、flask-unsign 工具使用
1. 在 Kali 中安装
sudo apt update
sudo apt install pipx
pipx ensurepath
pipx install flask-unsign
执行 pipx ensurepath 后如果当前终端仍然找不到命令,可以重新打开终端,再检查:
flask-unsign --help
2. 解码 Cookie
flask-unsign --decode --cookie ".eyJ..."
解码成功只能说明 Cookie 格式可识别,不代表签名有效,也不代表已经获得了服务器密钥。
3. 伪造 Cookie
flask-unsign --sign -c '{"_user_id":"1"}' --secret "th3f1askisfunny"
生成后要替换的是 Cookie 的值,而不是把命令输出附加到原 Cookie 后面。
4. 使用字典测试 SECRET_KEY
虽然本题通过源码泄露直接拿到了密钥,但在授权靶场中也可以使用字典进行离线测试:
flask-unsign --unsign --cookie ".eyJ..." --wordlist 字典.txt
参数说明:
--unsign:使用候选密钥验证已有 Cookie 的签名。--cookie:指定服务器签发的原始 Cookie。--wordlist:指定每行包含一个候选密钥的字典文件。
字典没有命中,只能说明候选列表中没有正确密钥,不能证明服务端密钥足够安全。
四、复盘 Tips
- Flask Session Cookie 不是 JWT。 两者都可能表现为客户端携带的点分隔字符串,但编码结构、签名算法和处理工具不同。
- 能解码不等于能篡改。 Flask 默认 Session 内容可以被读取,完整性主要由签名保护;修改后必须使用正确的
SECRET_KEY重新签名。 - 优先保留原始 Session 字段。 只修改与身份相关的字段,能够减少因为缺失
_fresh、_id等状态字段导致的失败。 - 泄露
SECRET_KEY的影响不只是一道题。 同一密钥如果用于多个环境或其他安全功能,泄露后的影响范围会进一步扩大。 - 修复时应更换已泄露密钥。 仅删除泄露页面不够,还应立即轮换
SECRET_KEY,使此前签发及伪造的 Cookie 失效,并排查源码和日志中的泄露来源。