防止令牌窃取

Lobsters Hottest 新闻

摘要

本文讨论了信息窃取型恶意软件窃取身份验证令牌的问题,并探讨了Dirk Balfanz在15年前提出的一项提案:使用自签名客户端证书进行TLS双向认证,从而将令牌绑定到特定设备,即使令牌被窃取也无法重用。

<p><a href="https://lobste.rs/s/agwtde/preventing_token_theft">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/07/02 12:11

# 防止令牌窃取 来源:https://codon.org.uk/~mjg59/blog/p/preventing-token-theft/ 当你登录某个服务时,你会获得一个身份验证令牌。之后的每个请求都包含这个令牌,使服务器能够识别你的身份,并确保你有权访问自己的数据。根据站点策略,令牌可能存储在内存中(因此重启浏览器后便会消失),也可能存储在磁盘上。令牌是你身份的证明。对于站点而言,任何拥有你令牌的人就是你本人。这些令牌可能是传统的浏览器 cookie,也可能存储在站点的本地存储中,或者(如果你不是在使用浏览器)存储在其他存储位置。 近年来,我们看到了像 LummaC2 (https://www.cisa.gov/news-events/cybersecurity-advisories/aa25-141b) 这样的信息窃取型恶意软件具备了窃取用户令牌的能力,这使得攻击者无需持续访问用户机器就能获取用户数据。即使站点采用了强多因素认证(MFA),这种攻击仍然有效,因此通行密钥(passkeys)也帮不上忙。对磁盘上的令牌进行加密也无法阻止恶意软件从浏览器内存中提取它们,或者获取用于加密的密钥。这似乎是一个相当棘手的问题。 但这并没有阻止人们尝试解决!Dirk Balfanz 编写了一份 IETF 草案,描述了如何使用**自签名证书进行 TLS 认证** (https://datatracker.ietf.org/doc/html/draft-balfanz-tls-obc-01)。该方案利用了 TLS 协议的**双向认证** (https://en.wikipedia.org/wiki/Mutual_authentication#mTLS) 特性,要求双方相互证明身份。在常规 TLS 中,远程站点会出示一个签名证书来表明自己的身份。而在双向认证时,你需要向远程站点出示一个证书,告诉它**你**是谁。这些客户端证书除企业环境外很少使用,因为部署它们**极其**麻烦。不是说它有点尖锐的边缘,而是它完全由尖锐的边缘构成。管理设备上的证书部署非常困难。如果浏览器下的证书发生更改,浏览器会感到困惑。你只有一个证书并且它会永久存在,因此你向其出示该证书的站点可以追踪你的身份。用户会被提示选择一个证书进行认证,如果他们选错了,所有事情都会崩溃,并且难以恢复。我部署过这个方案,体验非常糟糕。 但 Balfanz 的想法很简单。与其要求部署证书,不如让浏览器动态生成一个证书。目标不是以任何全局方式证明设备或用户的身份——而是将 TLS 会话与特定证书关联起来。例如,你可以将证书的哈希值包含在 cookie 中,如果有人试图在不提供该证书的情况下使用这个 cookie,则可以拒绝该 cookie。如果浏览器为证书使用了硬件支持的私钥,那么攻击者就不可能窃取它。当然,你仍然可以窃取 cookie,但无法使用它们。 这大约是 15 年前写的,看起来简单、优雅且功能完备。但它并没有实现。部分原因是,嗯,它并不那么简单。其中一个问题是隐私相关。Cookie 只在 TLS 会话建立后才发送,因此任何监控网络的人都不会了解用户身份的任何信息。这种方法的简单实现意味着客户端证书会在会话建立前发送,用户身份就可能被追踪(如果基于 TLS 1.3 实现,这不再是问题,但那是很久以前的事了)。通过重新排序客户端握手可以避免这个问题,但这意味着必须修改 TLS 规范,并且所有实现都必须更新以支持这一点。另一个困难是确定证书的粒度。你需要为每个站点使用不同的证书,以避免它们实际上成为追踪 cookie,但证书需要在 cookie 设置之前提供,并且你不知道站点将在其 cookie 中设置什么源。如果你为 `a.example.com` 生成一个证书,为 `b.example.com` 生成另一个证书,而 `a.example.com` 为 `*.example.com` 设置了一个 cookie 并包含了你在 `a.example.com` 上使用的证书,那么该 cookie 在 `b.example.com` 上就无法工作,事情就乱套了。这意味着支持它并不像看起来那么简单——你需要确保 cookie 的作用域与证书的作用域兼容。通过将其与**公共后缀列表** (https://publicsuffix.org/) 对齐,可能可以使其足够好地工作,但仍然存在期望不一致的风险。 而且,也许最重要的是,**TLS 会话恢复** (https://datatracker.ietf.org/doc/html/rfc5077)(在 TLS 1.3 中被**预共享密钥** (https://datatracker.ietf.org/doc/html/rfc8446#page-15) 取代)在某种程度上破坏了这一目标——客户端存储了允许它们在不执行证书交换的情况下重新建立 TLS 连接的状态(这减少了连接中断、切换网络等情况下的开销),而任何能够窃取 cookie 的人同样可以窃取该状态。 随后的尝试是**通道 ID** (https://datatracker.ietf.org/doc/html/draft-balfanz-tls-channelid-01)。这在一定程度上简化了实现——不再使用证书,而是发送原始公钥,同时以对 TLS 握手部分内容的签名形式提供私钥持有证明。即使在会话恢复时也需要进行此操作,从而避免了担心会话秘密被窃取的问题。交换的时间点在加密会话建立之后,因此用户身份也不会被泄露。然后可以将 cookie 绑定到这个标识符。不幸的是,它并没有真正解决如何将密钥范围与 cookie 要求匹配的问题,并且规范建议正确的处理方式是将密钥范围限定在顶级域(TLD),这将在站点间实现用户追踪(Chrome 的实现似乎将其限制在 `eTLD+1`,这与第三方 cookie 政策相匹配并避免了追踪风险)。 Chrome 曾支持此功能,但**在 2018 年初被移除** (https://groups.google.com/a/chromium.org/g/net-dev/c/AjFQjBmaEQE/m/gIXoV3IFCQAJ?utm_medium=email&utm_source=footer)。该消息中关于一些痛点讨论很有意思,明确指出跨域连接合并问题以及与零往返时间(0-RTT)TLS 1.3 的不兼容性。当时的总体共识似乎是,试图完全在 TLS 层解决这个问题有太多粗糙的边缘,应该采用不同的方法。 于是,在最初的源绑定证书草案发布大约 7 年后,我们迎来了**令牌绑定** (https://datatracker.ietf.org/doc/html/rfc8471)。这最终成了一件相当复杂的事情,涵盖了 3 个不同的 RFC,分别描述它如何影响 TLS、如何将其整合到 HTTP 中,以及如何管理涉及该过程的所有各方。简而言之,它与通道 ID 非常相似,只是还提供了一种允许令牌绑定到一方并由另一方消费的机制,从而避免了广泛范围的密钥需求。令牌绑定实际上解决了原提案中的所有问题,但代价是复杂度更高。 该 RFC 于 2018 年 10 月定稿。Chrome 在 2018 年 11 月移除了其对令牌绑定的(不完整的、草案阶段的)支持。Edge 一直支持到 2024 年末。尽管走完了整个 RFC 流程,但它在功能上已经死亡。 在此之前,整个过程主要由谷歌发起,微软在令牌绑定标准方面做出了重大贡献。这些工作专注于为问题找出通用解决方案,而不是将其绑定到任何特定的认证流程。下一步走向了不同的方向——与其试图为整个互联网解决这个问题,不如尝试为 OAuth 解决这个问题? RFC 8705 (https://datatracker.ietf.org/doc/html/rfc8705) 的标题是“OAuth 2.0 双向 TLS 客户端认证和证书绑定的访问令牌”。这基本上是 2011 年的方法,但 (a) 明确定义了如何将证书整合到颁发的认证 cookie 中,并且 (b) 附带了一个条件:嗯,如果你要使用由你的 IdP 颁发的令牌来向其他人认证,那么你需要为两者使用相同的证书。这对于公司拥有的笔记本电脑的情况可能没问题,因为多个站点能够关联身份是可以接受的(这恰恰是重点!),并且也适用于“我在使用应用而非浏览器”的情况,但不适用于更通用的场景。而且它似乎完全没有考虑会话恢复的情况?对 RFC 8705 的支持似乎很差,据我所知,在大型厂商中只有 Auth0 实现了它。理论上它适用于自签名客户端证书,但现实中,在所有平台上支持它几乎和一开始就颁发正确的客户端证书一样困难,因此部署会有点麻烦。但好消息是它不依赖任何 TLS 扩展或自定义浏览器行为,因此在客户端侧,它可以与任何浏览器正常工作。 这引出了 RFC 9449 (https://datatracker.ietf.org/doc/html/rfc9449),“展示持有证明(DPoP)”。与 RFC 8705 相比,它在降低部署负担方面更进一步——它可以与现有浏览器一起正常工作,**而且**甚至不需要任何证书。客户端生成一个密钥对,并在请求 cookie 时提供公钥。cookie 包含公钥。现在,每个对服务的请求都提供带有公钥的 cookie,同时还提供对 URI 和 HTTP 方法的签名。如果签名与令牌中的公钥匹配,那么很明显签名来自于被颁发令牌的机器,一切正常。 不过,这也有一些缺点。首先,它使用浏览器接口生成密钥(典型的如 `crypto.subtle.generateKey()` (https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey)),据我所知,没有任何浏览器能保证密钥在硬件中生成,即使它被标记为不可导出,因此任何能够窃取 cookie 的人也能窃取密钥。其次,签名只覆盖 URI 和 HTTP 方法,而不覆盖消息内容或其他头部,因此任何能够外泄有效签名的人都可以针对相同的 URI 使用不同的消息内容进行重放。推荐的应对方法是拒绝任何在最近几秒内未生成的签名,这又为时钟偏差带来糟糕的一天提供了一种绝佳方式。第三,每个单独的请求都必须单独签名,这本身不是问题,因为计算机速度快且具有多核,但如果你试图通过将密钥放在 TPM(受信任平台模块)中来解决第一个问题,那么你将面对一个速度慢且单线程的东西——这对于使用客户端证书来说可能可以接受(因为每个会话只需要一个签名,并且你可以对多个请求使用同一个会话),但如果用户打开一个浏览器,恢复了之前的标签页,而每个标签页都是一个 Web 应用,同时发出 100 个请求,那么这恐怕就不行了。 以防表述不清,我不喜欢 DPoP。它感觉并没有真正解决我们在现实世界中看到的基本问题(在恶意软件可以获取令牌的上下文中,它也能获取密钥),它增加了大量开销,并且内置了重放漏洞。我不知道它为什么存在,并且我对那些告诉我它能解决我的问题的供应商极度怀疑,因为如果他们这样说,那么我最终会假设他们要么不了解我的问题,要么不了解他们的技术,而这两者都不好。 不过,接下来我们终于来到了促使我写这篇文章的东西——Chrome 宣布他们已推出**设备绑定会话凭证**(device-bound session credentials)(https://security.googleblog.com/2026/04/protecting-cookies-with-device-bound.html)。这很有趣,因为这是一个 Chrome 功能,明确旨在对抗设备上的恶意软件,而这在 2018 年令牌绑定被移除时是超出范围的。由于这是整个 Web 层面的功能,它不必是 RFC,而是由 W3C (https://w3c.github.io/webappsec-dbsc/) 定义。我将略过所有复杂性,基本上这是一种在颁发 cookie 时注册公钥,然后在需要续期 cookie 时证明拥有私钥的方法。通过使 cookie 具有短暂的生命周期并支持在后台轮换它们,对用户的影响基本上为零;虽然攻击者仍然可以外泄并使用 cookie,但他们只能在短短的时间窗口内使用,直到需要刷新——而攻击者无法做到这一点,因为他们没有私钥。这避免了 DPoP 的开销,因为你只需要在每个 cookie 的整个生命周期内签名一次,而不是在每个请求上都签名。我**不喜欢**这个方案,因为存在被外泄的令牌仍可被使用的窗口,但感觉它比现状有了严格的改进。一个名为**企业版设备绑定会话凭证** (https://github.com/w3c/webappsec-dbsc/blob/main/DBSCE/Overview.md) 的扩展允许预先注册设备密钥,因此即使实际的运行时 DBSCE 流程不涉及证书,在企业环境中也可以使用证书进行设备注册,从而确保认证 cookie 只发送给受信任的设备。不幸的是,这是 Chrome 独有的,因此我们需要等待它被移植到所有随机的应用框架,才能在移动设备上得到广泛支持,或者几乎所有使用 Electron 包装的桌面应用。Mozilla 的**当前立场** (https://github.com/mozilla/standards-positions/issues/912#issuecomment-4840591341) 是不赞成此功能,所以我认为我们需观望 Safari 在广泛采用方面的立场。 我列表中的最后一项是**另一个客户端证书 / OAuth 绑定** (https://datatracker.ietf.org/doc/draft-mw-oauth-tls-session-bound-tokens/04/),在撰写本文时仍处于草案状态。这一主要针对代理驱动工具的使用场景,即在后台运行某些东西,使用一大堆各自代表你行动的工具。分别向所有工具进行认证并不好玩,但向非确定性代理授予广泛范围的访问令牌并信任它永远不会将它们发布到公共位置也不好。它与 RFC 8705 的关键区别在于,它目标是**连接**而非**会话**,从而避免了对会话恢复的担忧。这是通过 **TLS 导出器** (https://datatracker.ietf.org/doc/html/rfc5705) 实现的,在 TLS 1.3 中,即使通过会话恢复,导出器也应该是连接独有的(TLS 1.2 可能会在会话恢复中为导出器重用相同的密钥材料,因此建议对此强制使用 1.3)。通过在每次新连接时连同 cookie 一起提供一个新的签名,客户端证明了它仍然拥有对

相似文章

保障代理身份安全

Lobsters Hottest

一位安全专家讨论了为访问敏感资源的LLM代理保障身份令牌安全的挑战,并提出了一种基于代理的方法,将令牌绑定到特定环境以防止凭证窃取。

绝不浪费一个token(15分钟阅读)

TLDR AI

一篇技术博客文章,解释如何通过在代理和提供商之间放置一个持久缓冲区来避免浪费LLM token,从而在进程崩溃时无需重新获取已生成的token即可恢复。

为何不依赖数据库进行身份验证

Hacker News Top

本文通过一个SQL注入场景,解释了将数据库作为API身份验证唯一可信源的危险,并介绍了Sturdy Statistics采用的防御深度方法:使用HMAC-SHA512与加密胡椒。

最后一个Token之前:诊断最终Token安全探针的故障

arXiv cs.LG

本文研究了最终Token安全探针在越狱提示上的失败,发现有害内容可以分布在较早的Token中,并被最终读取忽略。它提出了一种PCA-HMM轨迹模型作为诊断工具,该模型能够恢复许多遗漏,而不会产生简单Token池化的误报。