TT Lab
开始
学习 学习路径 课程

会话与令牌 — 从浏览器到服务网格

Redis 会话与会话固定防御

在 TT Lab 中继续学习

目标

搭建一个以 Redis 作为会话存储的登录服务器,并用真实的 HTTP 请求确认:登录瞬间会话 ID 会被重新签发、绝对过期与 idle 无关地生效、退出登录会立即使会话失效。

为什么重要

服务器会话的状态由服务器掌握,浏览器只拿到钥匙。所以一次退出登录就能立即使其失效,但如果这把钥匙在登录前后没有变化,别人就能借助预先种下的钥匙搭上我的登录,会话固定便由此成立。OWASP 明确要求,权限级别从匿名变为已认证的瞬间要重新签发会话 ID。另外,过期必须同时设置 idle(无活动则过期,每次请求刷新)和 absolute(创建时间过久则无条件过期)两种;仅靠 Redis TTL 无法表示 absolute,所以要在值里另外存放创建时间。本实验让你亲自证明这些规则在真实请求中是否得到遵守。

步骤

  1. 在 127.0.0.1:8302 上启动 /root/st/session/app.py。GET /health 返回 200 和 {"ok":true}。
  2. 确认会话 Cookie 的名称是 __Host-sid,并带有 Secure、HttpOnly、SameSite、Path=/,将 Set-Cookie 头保存为一行,写入 /root/st/session/cookie.txt。
  3. 获取匿名会话,确认其 ID 以 sess:<id> 为键存在于 Redis 中,并且有 TTL 和 user,把结果写入 /root/st/session/store.txt。
  4. 带着匿名会话 ID 直接登录,确认响应给出的是另一个会话 ID,且旧 ID 消失,把结果写入 /root/st/session/fixation.txt。
  5. 确认登录会话的 TTL 不超过 idle 上限,并且在发送请求后会重新充满,把结果写入 /root/st/session/idle_ttl.txt。
  6. 确认把 created 设为过去时间的会话,即使 idle TTL 还有剩余,也不被认作已认证,把结果写入 /root/st/session/absolute.txt。
  7. 确认退出登录后,同一个 Cookie 会降为匿名,且存储中的键消失,把结果写入 /root/st/session/logout.txt。
  8. 用 /root/st/session/e2e.sh 测试整个流程,在 /root/st/session/e2e.out 中留下四行。

参考

启动 Redis 会话服务器

在 127.0.0.1:8302 上启动 /root/st/session/app.py。GET /health 返回 200 和 {"ok":true}。

Redis 已经在 6379 端口运行。把服务器放到后台运行,并轮询到它就绪为止。

检查会话 Cookie 属性

确认会话 Cookie 的名称是 __Host-sid,并带有 Secure、HttpOnly、SameSite、Path=/,将 Set-Cookie 头保存为一行,写入 /root/st/session/cookie.txt。

__Host- 前缀要求 Secure 和 Path=/,且不能加 Domain。用 curl -D - 查看响应头。

会话是否存储在 Redis 中

获取匿名会话,确认其 ID 以 sess:<id> 为键存在于 Redis 中,并且有 TTL 和 user,按 sid= redis_key= exists=1 ttl=<양수> user=(anon)(占位符为正数)的格式写入 /root/st/session/store.txt。

用 Cookie 罐(-c)获取会话 ID,再用 redis-cli EXISTS/TTL/GET 确认。

登录时重新签发会话 ID

带着匿名会话 ID 直接登录,确认响应给出的是另一个会话 ID,且旧 ID 从存储中消失,按 before= after= changed=yes old_gone=yes 的格式写入 /root/st/session/fixation.txt。

这是防御会话固定的核心。登录成功时必须删除(delete)旧会话并签发新 ID,才会出现 changed=yes。

idle 超时是 sliding TTL

确认登录会话的 Redis TTL 不超过 idle 上限,并且再发送一次请求后 TTL 会重新充满,按 ttl_before= ttl_after_request= 的格式写入 /root/st/session/idle_ttl.txt。两者都必须是正数,且 after 要接近上限。

每次请求用 EXPIRE 重新推迟 TTL,就成了 sliding idle。

absolute 过期与 TTL 无关

把 created 设为早于 absolute 上限的时间,同时让 idle TTL 保留得很充裕,确认这样的会话在 GET /whoami 中不被认作已认证,按 code=200 auth=False gone=yes 的格式写入 /root/st/session/absolute.txt。

在 load_session 中,只要 now - created > ABSOLUTE_SEC,即使 idle TTL 还有剩余也必须拒绝。

退出登录立即失效

登录后退出登录,再用同一个 Cookie 调用 GET /whoami,确认它降为匿名且存储中的键消失,按 revoked_auth=False key_gone=yes 的格式写入 /root/st/session/logout.txt。

状态由服务器掌握,所以删除一个键就会立即失效——这是与 JWT 的区别。

一次性证明完整流程

用 /root/st/session/e2e.sh 依次测试匿名→登录时重新签发→认证→退出登录后失效,并在 /root/st/session/e2e.out 中留下 reissue=yes、old_gone=yes、logged_in=True、after_logout=False。

把前面各步用一个脚本串起来。四行必须全部输出。