跨站登录检测利用计时、错误事件和重定向,推断你登录了哪些银行、论坛等服务,并说明浏览器如何用存储分区和第三方 Cookie 拦截来堵住这类泄露。
核心要点
- 登录状态是一条侧信道。 同源策略能阻止一个网站读取你在另一个源上的 Cookie 或会话数据,但它从未承诺隐藏“某个资源是否因为你在别处登录而表现不同”。
- 资源与计时探测利用缓存和加载耗时:一个只有在登录会话下才会快速加载(甚至才能加载成功)的 URL,每被攻击者页面请求一次,就会泄露一比特信息。
- 基于重定向和错误的检测会监听隐藏的
<img>、<script>或<link>指向已登录才能访问的接口时触发的onload/onerror事件,或 CSP 违规报告——登录与未登录的会话会产生不同、可被观察到的结果。 - 它会暴露什么:把这个信号在几十个网站上关联起来,就能拼出你使用哪家银行、哪个健康平台、哪个论坛或哪个交友软件的画像——全程不需要知道你的名字,这正是它对精准钓鱼极具价值的原因。
- Cookie 拦截、存储分区与缓存分区堵住了其中大部分漏洞。 一旦浏览器不再把某个网站的会话 Cookie 附加到跨站请求上,被探测的资源无论你实际是否登录,都会表现为“已登出”。Chrome 是个部分例外:它默认启用了缓存与存储分区,但除非你自己开启拦截,否则仍会照常发送第三方 Cookie。
跨站登录检测究竟是什么
跨站登录检测属于浏览器侧信道的一类,业内称为 XS-Leaks(跨站泄露)。同源策略非常擅长做一件事:阻止 attacker.example 读取来自 bank.example 响应的内容。但它从来没打算阻止 attacker.example 发起这个请求并观察它的行为方式——而单凭行为,往往就足以回答“这位访客现在是否登录了 bank.example”这样一个是非问题。
一个最简形式是这样的:攻击者的页面里嵌入一个隐藏的 <img> 标签,其 src 指向 https://bank.example/account/avatar.png。如果你登录了这家银行,浏览器会自动附上会话 Cookie,服务器返回一张真实图片,load 事件触发。如果你没有登录,同一个请求会被重定向到登录页或直接被拒绝,响应不是有效图片,于是触发的是 error 事件。攻击者从头到尾看不到你的账户细节——他们只需要知道哪个事件触发了。MDN 关于跨站泄露(XS-Leaks)的概述把这项技术连同下文的其他手法,归类为一类持续存在的浏览器安全问题,而不是某个一次性的漏洞。
针对已知接口的资源与计时探测
上面这种基于事件的形式还只是最粗糙的版本。更隐蔽的一类手法依赖的是计时而非清晰的成败信号。浏览器暴露了 Resource Timing API(performance.getEntriesByType('resource')),它会报告一次请求耗时多久,对于同源或经 Timing-Allow-Origin 允许的资源,还会报告传输了多少字节。这让攻击有两条路可走:
- 缓存计时探测。 如果某个资源只有正在使用目标网站的登录用户才会请求到、因而才会被缓存(比如仅登录后才加载的脚本包,或嵌入页面中的已登录 API 响应),那么攻击者页面发起的后续请求,对这些用户几乎会瞬间命中缓存,而对其他人则会走一遍网络请求。单凭耗时的差异就能回答登录与否的问题。
- 帧数与请求数统计。 有些登录后才能访问的页面,比未登录版本渲染出更多子资源(额外的组件、个性化模块、更多重定向)。统计一次页面加载触发了多少请求或帧,不用读取任何响应正文就能区分这两种状态。其中
window.length——一个窗口里的帧数量——是 Web 平台有意允许跨源读取的少数几个属性之一,所以攻击者只要用弹出窗口或 iframe 打开目标页面,即便该文档的其他一切细节都不可见,也仍能读到它的帧数。
这两种探测都不需要访问目标源的 JavaScript 权限——攻击者测量的一切都发生在自己页面的执行上下文里,这正是这类手法能存活这么久的原因:在浏览器看来,它们不像是一次同源违规。
基于重定向和错误的检测
重定向变体把同样的思路又推进了一步。攻击者可以不只依赖 onerror,而是给请求配上一条严格的 Content-Security-Policy,只允许连接到该资源预期所在的源,然后监听 securitypolicyviolation 事件。如果一个未登录的请求被重定向到了托管在另一个源上的登录页,CSP 会拦下这次重定向并触发一次违规,攻击者就能观察到。而一个已登录的请求会直接返回资源、没有任何重定向,自然不会触发该策略。这一个事件出现与否,就成了那个“神谕”——这是安全研究者多年来在众多网站上归纳出的基于错误事件和 CSP 的 XS-Leak 模式之一。
重定向探测之所以经久不衰,是因为它不依赖目标网站代码里的任何特定漏洞——它只依赖目标网站在响应链的某个环节上,对已登录和未登录的访客表现出不同行为,而这几乎是任何带登录门槛的服务都具备的特征。
它会暴露关于你的什么信息
单单一个“是/否”的答案——“是否登录了 bank.example:是”——本身并不算多敏感。但当攻击者把同一套探测拿去针对几十上百个已知网站运行时,风险就会成倍累加:银行、医疗平台、交友软件、与特定政治或宗教群体相关的论坛、成人网站、公司内网。由此得到的一张“已登录”标记位图,是一种独立于——而且往往比——我们在浏览器指纹指南中介绍的设备指纹更敏感的行为指纹,因为它直接点出了你在用哪些服务,而不只是缩小你在用哪台设备的范围。
这份画像对精准钓鱼立刻就有用:一封邮件如果准确点出你真正开户的那家银行,而不是“工商银行或招商银行,随便猜一个”,可信度会高出一大截。它本身也是一种去匿名化的手段——一个人具体登录了哪些服务的组合,识别力几乎能与设备指纹相当,而且不带任何 Canvas 或 WebGL 读数所附带的那些技术噪声。
浏览器的防御:分区、Cookie 拦截与 Fetch Metadata
结构性的修复办法,不是逐个修补有漏洞的接口——一个网站可能有成千上万个这样的接口——而是移除这些手法大多赖以生存的浏览器行为:跨站请求会悄悄带上目标网站的会话 Cookie。
第三方 Cookie 拦截与存储分区做的正是这件事。Firefox 的全面 Cookie 保护会给每个第三方资源分配一个以顶层站点为键的独立 Cookie 罐,所以当 attacker.example 请求资源时,bank.example 的会话 Cookie 根本不会被附加——被探测的资源无论你的真实会话状态如何,都会无条件地表现为已登出。Safari 的全面第三方 Cookie 拦截通过默认拦截而非分区键的方式,达到了同样的终态。Chrome 则是一个值得留意的部分例外:它同样上线了自己的存储分区机制,外加一个可选启用的 CHIPS 机制,用来满足那些仍需要一点跨页面状态、但并非跨站追踪的合法场景(嵌入式小组件、CDN 负载均衡等)——但在几度推迟之后,Google 放弃了让所有人都停用第三方 Cookie 的计划。默认的 Chrome 窗口仍会照常附加第三方 Cookie,只有无痕窗口才会拦截。
不过 Cookie 并不是故事的全部。缓存计时探测在探测请求本身上根本不需要 Cookie——它读取的是你先前登录访问时留下的缓存条目。对付它的是另一套机制:如今浏览器都会按顶层站点对 HTTP 缓存做分区,你在 bank.example 上访问时缓存下来的资源,与从 attacker.example 的页面请求同一个 URL 时所用的键并不相同,攻击者的请求必然落空。Chrome 与 Firefox 都在 2020 至 2021 年间上线了这项机制,WebKit 对网络状态做分区的时间还要更早——这正是为什么即便在默认仍会发送第三方 Cookie 的浏览器里,缓存探测也已日渐失效。
一个互补的服务端防御手段是 Fetch Metadata 请求头,尤其是 Sec-Fetch-Site。由于这些请求头由浏览器生成、无法从 JavaScript 伪造,服务器可以检查一个指向敏感的已登录接口的请求上是否带有 Sec-Fetch-Site: cross-site,并直接拒绝提供服务——从拥有数据的源头上堵住这个漏洞,而不必管访客用的是哪款浏览器。这两种防御是相辅相成的:分区机制能保护你,即便你访问的是尚未采用 Fetch Metadata 的网站;而 Fetch Metadata 能保护那些默认仍会发送第三方 Cookie 的浏览器用户。
综合来看
跨站登录检测,是我们在机器人检测指南中介绍的那类被动、事件驱动观察手法的近亲——两者都依赖观察加载耗时、错误事件和响应行为,而不是直接读取任何本不该被看到的内容。而且它并不是取代设备指纹,而是与之叠加放大:知道哪台设备和哪些账号都指向同一个访客,是比二者单独更强的信号,这也是为什么理解指纹信号如何叠加很重要——即便这里讨论的具体泄露针对的是登录状态,而非硬件特征。
好消息是,这是少数几种真正能靠浏览器本身修复、而不需要你改变个人习惯的追踪手法之一。在默认开启防护的当前版本 Firefox 或 Safari 上,上文那些依赖 Cookie 的探测手法已经失效——它们赖以生存的会话 Cookie,根本不会离开设置它的那个网站。而在 Chrome 上,缓存计时探测同样已被默认关闭,但基于 Cookie 的那一类要等你自己开启第三方 Cookie 拦截之后才会失效——这也让这一项设置成为你针对整类泄露能做的、性价比最高的改动。
登录状态本身是页面无法直接展示给你的——探测发生在别人的网站上,而且按其设计,你永远看不到它的答案。你真正能检查的,是攻击者会拿来与之组合的其余那部分信号面:BrowserInsight 的指纹检测会列出你的浏览器当前暴露的 Canvas、WebGL、字体与存储等信号,全部在你的浏览器本地完成测量,绝不会被发送到任何地方做分析。
常见问题
网站真的能靠我访问一个页面,就知道我是否登录了自己的银行账户吗?
用上文的手法,以前确实可以稳定做到。而在默认拦截第三方 Cookie 的浏览器中——当前版本的 Firefox 和 Safari 都是如此——浏览器不再会把你银行的会话 Cookie 附加到跨站请求上,所以被探测的资源无论你的真实会话如何都会表现为已登出,银行方无需做任何改动。Chrome 是目前留下的缺口:缓存与存储分区默认开启,能封死那些计时探测,但除非你在隐私设置里手动开启拦截、或使用无痕模式,第三方 Cookie 仍会照常发送。
隐私/无痕浏览能阻止登录检测吗?
大体上能,但原因是间接的:你在无痕窗口里通常本来就没登录任何服务,所以每一次探测得到的“未登录”都是实话。除此之外,无痕模式并不会改变规则——在一次活跃的无痕会话内部,跨站 Cookie 的行为仍取决于和普通浏览一样的第三方 Cookie 与分区设置。唯一值得注意的例外是 Chrome:它在无痕模式下默认拦截第三方 Cookie,普通窗口却不会,所以在 Chrome 上,无痕窗口确实能堵住普通窗口敞着的那些探测。
这和浏览器指纹识别是一回事吗?
不是,而且区别很关键。指纹识别是通过字体、GPU 输出、屏幕尺寸等技术属性识别你的设备。登录检测识别的是你持有哪些账号,依靠的是浏览器处理 Cookie 的行为方式,而不是任何设备属性。一个同时掌握两种信号的攻击者,能把一台具体的设备和一组具体的现实服务对应起来——两个信号叠加使用,远比单独使用任何一个更强大。
网站能不能不等浏览器,自己就把这个问题解决掉?
可以部分解决。采用 Fetch Metadata 请求头可以让网站直接拒绝对已登录接口的跨站请求,这能为每一位访客——无论用什么浏览器——堵住这个漏洞。但这需要这家特定网站在每一个敏感接口上都正确实现它,这也是为什么浏览器端的分区机制——即便网站没做这项工作也能保护访客——成了目前主要的防线。


