[NSSRound#13 Basic] Flask Session Cookie 伪造

记录 Flask 客户端 Session 的识别、解码、SECRET_KEY 泄露以及使用 flask-unsign 重新签名的过程。

  • #CTF
  • #Flask
  • #Session
  • #Cookie
  • #身份认证

本文记录授权 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 重新签名时的结果:

使用 flask-unsign 重新签名 Flask Session

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
flask-unsign --decode --cookie ".eyJ..."

解码成功只能说明 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

  1. Flask Session Cookie 不是 JWT。 两者都可能表现为客户端携带的点分隔字符串,但编码结构、签名算法和处理工具不同。
  2. 能解码不等于能篡改。 Flask 默认 Session 内容可以被读取,完整性主要由签名保护;修改后必须使用正确的 SECRET_KEY 重新签名。
  3. 优先保留原始 Session 字段。 只修改与身份相关的字段,能够减少因为缺失 _fresh、_id 等状态字段导致的失败。
  4. 泄露 SECRET_KEY 的影响不只是一道题。 同一密钥如果用于多个环境或其他安全功能,泄露后的影响范围会进一步扩大。
  5. 修复时应更换已泄露密钥。 仅删除泄露页面不够,还应立即轮换 SECRET_KEY,使此前签发及伪造的 Cookie 失效,并排查源码和日志中的泄露来源。