密码没泄露,2FA 也没失效:Chrome 146 为什么还要让 Cookie“搬不走”?

假设你半夜收到一封安全提醒:账号刚在另一座城市修改了资料。

你第一反应是查密码。密码管理器生成的随机密码没有泄露,登录记录里也没有新的短信验证码或身份验证器挑战。2FA 明明开着,攻击者为什么还能像本人一样操作?

一个容易被忽略的答案是:对方可能根本没有重新登录。

登录成功后,网站会给浏览器一张"已经验过身份"的通行证。它通常是由 Cookie 携带的 Session ID,或通过其他机制传输的 Access Token。后续请求带着凭证,服务端就不必每次再问密码和验证码。如果这张通行证被信息窃取恶意软件、浏览器配置窃取或其他手段带走,攻击者尝试的是复用现成会话,不是突破登录页面。

这类问题最近有了很具体的新进展。Google Workspace 已开始在满足条件的 Windows Chrome 146+ 新会话上自动应用设备绑定会话凭证(Device Bound Session Credentials,DBSC)。Chrome 的通用协议支持在 Windows 版 145 已经公布,Chrome 146+ 是 Workspace 当前部署要求。两项进展共同指向一个变化:登录态安全开始从"谁拿着凭证"走向"谁还能证明自己在原设备上"。

把开头的异地操作放回时间线,工程问题就变成了:Cookie 已经从原设备带走,服务端怎样缩短副本的可用时间,并阻止它在另一台电脑持续续期?

2FA 守住了门,攻击者可能拿走的是门内通行证

把认证过程拆成两个阶段,很多困惑会立刻消失。

登录认证阶段回答"此刻来的人是不是账号所有者"。密码、短信验证码、身份验证器、Passkey 都工作在这里。2FA 的价值很大:即使密码泄露,攻击者仍缺第二个因素。

会话连续阶段回答"这个已经登录的浏览器,后续请求是否还属于刚才那个人"。网站不可能每点一次按钮都要求重新刷脸,于是签发会话凭证。传统 Cookie 多数是 bearer credential:谁能把这个值放进正确请求里,谁就暂时拥有它代表的权限。

问题就在这里。攻击者若从登录阶段进入,需要面对密码和 2FA;若直接窃取登录后的会话凭证,服务端看到的可能只是一张已经验过身份的票。

服务端把 Session 数据存在 Redis 或数据库里,也不会天然解决这个问题。服务端状态方便吊销、续期和集中控制,但浏览器仍要带一个 Session ID 来指向那条状态。Session ID 被复制后,另一台设备照样可以拿它来查询同一条会话。

JWT 也没有自动更安全。签名能证明 Token 是服务端签发且没有被篡改,不能证明这次拿它来的人仍是原来的浏览器。把 Session ID 换成 JWT,只是凭证形态和验证位置变了,bearer 属性没有消失。

把异机重放摆到每项控制措施面前

沿着"副本已经到达另一台设备"这个前提逐项检查,HttpOnlySecureSameSite、短有效期和服务端吊销会呈现出清晰的能力分工。

措施 主要解决的问题 没有自动解决的问题
HttpOnly 降低页面 JavaScript 直接读取 Cookie 的风险 恶意软件读取浏览器数据、控制本机浏览器或其他非脚本窃取路径
Secure 只通过 HTTPS 发送 Cookie 凭证已经从终端被复制后的异机重放
SameSite 限制部分跨站请求自动携带 Cookie,降低 CSRF 风险 攻击者在自己的设备上主动设置被盗 Cookie
短有效期 缩短被盗凭证的可用窗口 窗口内的重放,以及长期 refresh credential 被盗
服务端 Session 方便吊销、风险控制和集中状态管理 被盗 Session ID 在吊销前仍可能被复用
设备指纹 提供异常信号 IP、User-Agent 会正常变化,也可能被模拟,存在误报和漏报

OWASP 的 Cookie 窃取缓解指南建议记录 IP、User-Agent、语言和部分客户端提示,用显著变化识别异常,再对敏感操作要求重新认证。它很实用,也诚实承认了局限:出差、换网络、浏览器升级会制造误报;攻击者处在相近网络环境时又会漏报。

这些方法的共同难点是,它们大多在"猜这次请求像不像原设备"。DBSC 换了一种问题:浏览器能否用原设备里的私钥,完成一次加密证明?

DBSC 的核心:短期门票必须由原设备续签

DBSC 没有抛弃 Cookie,也没有要求业务接口全部改成新鉴权协议。它在现有会话旁边增加了一对设备密钥和两个端点:注册端点与刷新端点。

完整流程可以按五个时刻理解。

1. 登录完成,服务端发起绑定

用户照常完成密码、2FA 或 Passkey 登录。下面采用当前 W3C Editor's Draft 的简化格式展示注册入口;该草案仍可能变化,实际接入应以当期规范与浏览器实现为准:

http 复制代码
Secure-Session-Registration: (ES256 RS256);path="/session/register";challenge="registration_challenge"
Set-Cookie: auth_session=long_lived_bootstrap; Secure; SameSite=Lax

这个 Header 告诉支持 DBSC 的 Chrome:为当前会话建立设备绑定。现有登录流程仍然负责确认用户身份,DBSC 从登录成功之后接手会话连续性。

2. 浏览器生成密钥,服务端只保存公钥

Chrome 在设备上生成公私钥对。Windows 环境具备条件时,私钥由 TPM 等安全硬件保护;服务端收到的是公钥与注册请求,不需要接触私钥。

可以把它类比成一张短期门票和一枚留在原设备里的印章。门票本身可以被拍照复制,印章不能跟着照片一起搬走。这个类比只解释方向,真正的安全性来自后面的 challenge-response,而不是"识别设备型号"。

注册端点把公钥与当前会话关联,并返回会话配置:哪些 Cookie 受管理、刷新地址是什么、作用域覆盖哪些路径。随后把长期通行证替换成短期 Cookie。Chrome 官方示例使用 10 分钟,只是示例值,生产环境应按业务风险和刷新成本决定。

一个最小会话记录可以包含:

text 复制代码
session_id
user_id
public_key_thumbprint
binding_state
cookie_generation
expires_at
last_refresh_at
risk_state
revoked_at

服务端仍然按普通 Cookie 读取 session_id,新增的是对会话"能否在原设备续期"的状态管理。

当受管理 Cookie 缺失或过期,Chrome 会先暂停发往业务接口的原请求,转而调用刷新端点。用户不需要手工点"重新登录",业务接口也不用理解私钥。

http 复制代码
POST /session/refresh
Sec-Secure-Session-Id: session_id

4. 服务端给 challenge,原设备用私钥签名

刷新端点先返回带 session id 的 challenge:

http 复制代码
HTTP/1.1 403 Forbidden
Secure-Session-Challenge: "recent_challenge";id="session_id"

Chrome 用设备私钥签名后重试。当前草案要求 proof 的 jti 复制 challenge;服务端查找该 session 的公钥与 recent challenges,确认签名正确并拒绝 stale challenge。网络延迟与并发可能让新 challenge 发出后才收到旧 challenge 的合法签名,因此服务端往往需要短期保留多枚 recent challenge,再用幂等和凭证代际控制避免重复副作用。

http 复制代码
POST /session/refresh
Sec-Secure-Session-Id: session_id
Secure-Session-Response: <signed-proof>

HTTP/1.1 200 OK
Set-Cookie: auth_session=next_short_grant; Max-Age=600; Secure; SameSite=Lax

5. Chrome 恢复原请求

新 Cookie 到手后,Chrome 恢复刚才暂停的业务请求。对大多数业务接口来说,它们看到的仍是熟悉的 Cookie,不需要在每个 Controller 里加一遍签名验证。

攻击者若把刚才窃取的短期 Cookie 复制到另一台机器,只能使用到它过期。刷新时没有原设备私钥,无法完成签名证明。长期会话价值被压缩成短窗口,异机重放的收益明显下降。

DBSC 的有效边界

"上了 DBSC,Cookie 就再也偷不走"是一句危险的结论。协议明确收窄的是被盗凭证离开原设备后的重放能力,它没有把终端和网站变成绝对安全环境。

同设备控制仍然危险。 如果恶意软件可以操纵原浏览器、注入进程或借用已经打开的会话执行操作,攻击仍发生在持有私钥的设备上。DBSC 不会替你清除恶意软件。

XSS 仍需要单独治理。 页面里的恶意脚本即使读不到 HttpOnly Cookie,也可能以当前用户身份发起站内操作。CSP、输出编码、可信类型、依赖治理和敏感操作确认仍然重要。

注册时受控是更难的边界。 Chrome 官方提醒,如果恶意软件在会话注册期间已存在,攻击复杂度会升高,但不能被描述成不可能。安全硬件提高的是窃取和复制门槛,不是给所有终端攻击判死刑。

服务端失陷不在它的主防区。 会话库、签名验证、challenge 生成或账号权限系统被攻破,需要另一套服务端防御、密钥治理和审计。

客户端能力与刷新故障需要站点策略。 设备没有安全密钥存储、TPM 忙碌、签名失败、刷新端点超时、第三方 Cookie 被禁用,都可能让绑定或刷新失败。Chrome 开发指南给出了可选的长期 fallback Cookie 集成模式;只有站点主动保留它时才存在这条路径。fallback 若可直接访问敏感接口,会重新引入 bearer replay 风险。没有 fallback 时,请求可能不带受保护 Cookie 到达服务端,由站点选择重新认证或拒绝。

最后一个区分也很重要:DBSC 不是 Passkey 替代品。Passkey 主要改善登录认证,DBSC 保护登录后的会话连续性。二者可以串在同一条安全链路上,职责不同。

上线决策集中在三张矩阵里

协议流程讲清后,一个 2000 万月活产品还要回答客户端能力、操作风险与证明失败原因如何组合。用固定四阶段模板容易掩盖真实选择,决策矩阵更接近生产现场。

客户端能力矩阵

客户端状态 普通浏览 敏感操作 观测重点
DBSC 绑定并可正常刷新 使用短期 Cookie 允许,持续风险判断 绑定成功率、refresh P50/P95
浏览器支持但 TPM 临时失败 站点可短时降级或要求重试 step-up 或重新登录 TPM 忙碌率、恢复时间
浏览器或硬件不支持 采用既有会话策略 依据风险要求强认证 不支持率、转化影响
会话已撤销或证明异常 结束会话 拒绝并重新认证 异机重放与撤销生效时间

操作风险矩阵

读取公开内容、浏览个人面板、修改恢复邮箱、导出数据、付款和管理员操作具有不同后果。站点可以让普通浏览在短暂刷新故障时继续,把设备绑定或 step-up 放在凭证修改、数据导出和资金相关动作前。这个分级属于站点推荐策略,不是 DBSC 协议替所有产品做出的统一决定。

证明失败矩阵

签名无效、challenge stale、refresh endpoint 超时、绑定记录缺失和账号已撤销不能合并成一个 401。前两类可能说明异机重放或并发时序,网络超时需要可恢复策略,记录缺失要检查数据一致性,账号撤销则必须结束会话。每类失败都应有独立审计原因与用户动作。

数据层通过 recent challenge 集合、凭证 generation 和原子更新处理并发。用户退出、管理员封禁或风险系统判定失陷时,同时撤销会话与绑定。上线观察绑定成功率、refresh P95、降级率、重新登录率、客服工单与异常重放信号;这些数据决定哪些矩阵单元可以逐步收紧。

用攻击时序组织面试回答

回答可以沿 登录 → 凭证被窃 → 异机重放 → 短期凭证到期 → 刷新证明失败 推进。每走到一个时刻,说明当前信任来自哪里:2FA 证明登录身份,Session ID 或 Access Token 表达已有授权,DBSC refresh proof 证明会话仍接触原设备密钥。

随后把异常插进同一时序:TPM 忙时普通浏览怎样处理,敏感操作何时 step-up,同设备恶意软件为何仍在防区之外,服务端如何让旧 challenge 与并发刷新保持幂等。这样一条回答自然覆盖协议、风险与运行状态,无需把内容压成背诵模板。

我会用 offer.cc 对这条状态机做反复追问:从哪一步开始信任设备、哪个失败原因允许恢复、哪类操作必须重新认证。练习目标是让每次状态迁移都能说出输入、证明、拒绝条件和观测指标。

异机重放测试矩阵

测试场景 预期结果
原设备持有效短期 Cookie 普通请求通过
另一台设备复制仍有效的短期 Cookie 到期前可能通过,到期后无法完成绑定刷新
原设备提供 recent challenge 的正确签名 签发下一代短期 Cookie
使用 stale challenge 或错误私钥 拒绝刷新并记录原因
多标签页并发刷新 保持幂等,不让旧 generation 覆盖新状态
TPM 临时不可用 按站点策略重试、受限降级或重新认证
用户退出或管理员撤销 绑定与会话同时失效
同设备恶意软件操纵浏览器 进入 DBSC 外的端点安全与行为风控范围

中文参考

W3C DBSC Editor's Draft 仍可能变化,本文的 Header 与并发 challenge 语义以该草案核对:

w3c.github.io/webappsec-d...

OWASP Cookie Theft Mitigation 为不可替代的一手安全指南,本文只用于核对设备信号误报、重认证与会话检测边界:

cheatsheetseries.owasp.org/cheatsheets...

相关推荐
小蚂蚁i1 小时前
React Hooks 原理深度解析:从会用到真正懂
前端·react.js
触底反弹1 小时前
Vue 实战:手把手教你实现 ChatGPT 同款打字机效果
前端·javascript·人工智能
BreezeJiang1 小时前
Vue 3 流式输出实战:ReadableStream + DeepSeek API 实现打字机对话
前端
用户EasyAdminBlazor2 小时前
EasyAdminBlazor 第十篇:数据导入导出——批量处理不再麻烦
前端·后端
饮茶三千2 小时前
电子保函模板编辑器实战二:HTML→FTL 编译转换的设计与实现
前端·vue.js·设计模式
ricardo19732 小时前
SSR / SSG 性能对比:Next.js 三种渲染模式实测数据
前端·面试
ikoala2 小时前
Codex 小白入门:从安装到插件、MCP、Skills,一篇把配置讲明白
前端·javascript·后端
雪隐2 小时前
用Flutter做背单词APP-04一个人,2周,从零撸出一个背单词App
前端·人工智能·后端
OpenTiny社区3 小时前
Skill 设计:封装、评测、运营,一次讲透
前端·github