返回知识中心
指纹

Navigator 属性指纹:浏览器信息泄露

platform、hardwareConcurrency、deviceMemory、languages 和 userAgentData 等 navigator 值如何组合成指纹信号,以及如何检查这些值是否保持一致。

BotBrowser Team

文档中心

想直接看维护中的产品文档?

这篇文章对应的主题已经有文档中心页面。需要规范流程、当前参数和长期参考时,优先看 docs。

页面在申请任何权限之前就能读取 navigator.platform、navigator.hardwareConcurrency、navigator.deviceMemory、navigator.languages 和 navigator.userAgentData,一个把这些值全部记录下来的脚本,等于拿到了请求背后那台机器的简要描述。BotBrowser 从一份配置文件中把这些值加载到受控上下文,因此主线程、Web Worker 和 Sec-CH-UA 请求头描述的是同一台设备。下面说明这些值如何组合成指纹信号、只改动一个值时它从哪里开始与相邻的值矛盾,以及如何赶在网站之前检查一致性。

TL;DR

  • navigator 的值无需权限提示就能读取,并且要放在一起判断,所以一个常见值出现在不常见的组合里,比单独一次读数更有分量。
  • userAgent、platform、vendor、userAgentData 和 Sec-CH-UA 请求头把同样的问题回答了两遍;只改其中一处,就会留下一处能被看到的分歧。
  • 在页面脚本里做的改动既到不了 Web Worker,也到不了请求头,因为这两者读取的是另一个作用域。
  • BotBrowser 从已加载的配置文件取 navigator 值,--bot-network-info-override 负责 navigator.connection,换一份配置文件比修正单个字段更可靠。

目录

哪些 navigator 值构成信号

navigator 对象是页面了解自身所在浏览器的最基本入口。读取它不需要权限提示,脚本一启动就能使用,而且大量普通的网页代码出于正当理由读取它,例如选择布局、确定 worker 池的大小,或挑选显示语言。正因为如此容易获得,它也成了指纹识别的标准输入。没有哪一个属性可以单独识别一个人,信号来自组合:脚本读取许多值,把它们合成一条记录,再与以前见过的记录比较。BotBrowser 可以在受控上下文中从同一份一致的配置加载 navigator 值,但不能改变站点如何组合和评估这些值。

W3C 关于在 Web 规范中缓解指纹识别的指南直接描述了这种模式。每增加一项浏览器能力,就多出一点脚本可以读取的差异,许多细小的差异加在一起,即使没有任何单个值显得异常,也足以区分不同的浏览器。因此,下面各节不会给出 navigator 值的固定唯一性数字。某个组合的可识别程度取决于网站实际接触到的浏览器群体,在一个群体上测得的数字不能套用到另一个群体。

已加载的配置文件为主线程、Web Worker 和 Sec-CH-UA 请求头提供数据,三处报告相同的平台、核心数、内存档位、语言和品牌。下方,UA 字符串显示 macOS,而 platform 仍为 Win32,二者形成不一致。

这张图展示了主题的两个方面。当同一份设备描述为每一个回答 navigator 问题的位置提供数据时,各处的答案一致。如果只改动了一个值,相关的值仍然描述原来的设备,这种不一致本身就成了一个信号。

身份类值:userAgent、platform 和 vendor

navigator.userAgent 是最古老、读取最频繁的值。它包含浏览器名称、版本和操作系统标记。基于 Chromium 的浏览器现在精简了这个字符串中的细节,操作系统版本和浏览器的次要版本不再写在其中,更细的信息转移到了 User-Agent Client Hints。navigator.platform 是一个简短的字符串,例如 "Win32"、"MacIntel" 或 "Linux x86_64"。MDN 将它标记为已弃用,但当前的浏览器仍然返回值,脚本也仍然读取它。navigator.vendor 返回供应商字符串,不同浏览器系列的值不同。

这三个值描述的是同样两件事,即这是哪个浏览器、运行在哪个操作系统上,因此脚本可以检查它们彼此是否一致。诚实的浏览器毫不费力就能通过这项检查,因为每个值都来自同一次安装。被修改过的浏览器只有在所有相关值都被一起修改时才能通过。

硬件、语言和连接类值

navigator.hardwareConcurrency 返回可用于运行线程的逻辑处理器数量。浏览器报告的数字可能比机器的真实数量更低或经过取整,因此应把它看作取决于硬件和浏览器的提示,而不是精确的清单。navigator.deviceMemory 是设备内存(以 GB 为单位)的近似值。它只存在于基于 Chromium 的浏览器中,只在安全上下文中可用,并且报告值上限为 8,因此内存更大的机器仍然报告 8。这两个值既可以在主页面读取,也可以在 Web Worker 内读取,这对本文后面的检查很重要。

navigator.language 返回首选语言,navigator.languages 返回按顺序排列的首选语言列表,例如 en-US 后面跟着 en。这个列表与 Accept-Language 请求头通常来自同一个浏览器设置,所以页面读到的值与服务器收到的值描述的是同样的偏好。过长、过短或与所报告地区不相称的列表,会让这个组合更容易被区分。

navigator.connection 在基于 Chromium 的浏览器中提供 effectiveType、downlink、rtt 和 saveData。这些值描述的是网络状况而不是硬件,对应的 Client Hints 请求头可以在请求中携带同样的信息。不同浏览器的支持情况不同,读数也会随网络变化,所以它们的表现与上面的硬件值不同。不过它们仍属于脚本所组装的同一条记录。

Client Hints 与 userAgentData

在基于 Chromium 的浏览器中,navigator.userAgentData 是 User-Agent 字符串的结构化对应物。它直接提供浏览器品牌列表、移动设备标志和平台名称。它的 getHighEntropyValues() 方法可按需返回更多细节,例如平台版本、架构、位数、型号和完整版本列表。同样的信息也通过 Sec-CH-UA 请求头传递。品牌列表、移动设备标志和平台默认就会发送,而熵更高的值只有在服务器通过 Accept-CH 响应头提出请求后才会发送。

因此有三个位置在回答关于同一个浏览器的问题:User-Agent 字符串、userAgentData 对象和 Client Hints 请求头。一个自洽的浏览器在这三处给出相同的答案,而脚本或服务器要对它们进行比较,只需要几行代码。

常见的配置被大量浏览器共享,不常见的配置则很少有人共享。典型笔记本的核心数、主流的平台字符串和常见的语言列表,单独来看说明不了什么。与消费级设备通常报告的数值相差很远的核心数、很少见的内存档位,或者不寻常的语言列表,会缩小可能产生这条记录的浏览器范围。具体影响取决于收集这些数据的网站的受众,所以这里不给出单一的数字。

为什么只改一个值会造成不一致

实际的教训是:navigator 的值并不相互独立。它们描述的是同一台机器,网站可以检查它们描述的是否是同一台。只改变一个值时,其余的值仍然描述原来的机器,它们之间的差异本身就是一个信号。

被修改的 User-Agent 字符串

替换 User-Agent 字符串是最古老的做法。假设某个扩展把 navigator.userAgent 改成 macOS 的字符串,而 navigator.platform 仍然返回 "Win32"。这两个值现在指向不同的操作系统。当 navigator.userAgentData 仍然报告原来的平台,或者 Client Hints 请求头由与 JavaScript 值不同的设置生成时,也会出现同样的问题。

网站不需要知道哪个值才是真的。它只需要看到这些值互相矛盾,而矛盾比任何一个单独的值都更具辨识度。

随机化的值

随机化也有类似的弱点。为每个会话随机选取核心数和内存值,会产生很少有真实设备具备的组合,例如移动平台字符串配上桌面级的核心数,或者与其余所报告硬件不相称的内存值。

每次访问都变化的值也通不过最简单的稳定性测试,因为真实的浏览器下一次报告的硬件数字是相同的。既不寻常又不稳定的值,比它们所取代的真实值更容易被区分。

在错误位置做出的修改

修改发生在哪里,与改了什么同样重要。注入页面的脚本只能影响主线程读到的内容。Web Worker 有自己的全局作用域和自己的 navigator,所以如果页面层面的改动没有覆盖到 worker,原来的值在那里依然可见。请求头由浏览器的网络代码生成,而不是由页面脚本生成,所以页面层面的改动永远不会改变它们。结果是本该只有一个答案的地方出现了三个答案。

其他常见措施同样解决不了这个信号。VPN 改变网络地址,navigator 的值保持原样。隐私浏览模式保持相同的硬件和语言值。清除 cookie 删除已存储的状态,但不会改变浏览器对自身的报告。

下面几条规则概括了一致性在实践中的含义:

  • User-Agent 字符串中的操作系统与 navigator.platform 以及 navigator.userAgentData 中的平台一致。
  • 浏览器品牌和主版本号在 User-Agent 字符串、userAgentData 品牌列表和 Sec-CH-UA 请求头之间一致。
  • 核心数和内存档位对于该平台所暗示的设备类别是合理的。
  • navigator.languages 对所报告的区域设置是合理的,并且与 Accept-Language 请求头一致。
  • 以上每一个值在主线程、在 worker 中以及在重复的会话之间都相同。

在线程和请求头之间验证一致性

验证的目的不是找到一个正确的数字,而是确认每一个回答 navigator 问题的位置给出的答案都相同。下面的检查只需要一个浏览器和一个由你控制的测试页面,不需要访问任何第三方网站。

主线程与 Web Worker

打开一个你控制的页面,在主线程读取这些值:User-Agent 字符串、平台、vendor、逻辑处理器数量、设备内存、语言列表,以及来自 navigator.userAgentData 的品牌列表和平台。把它们记录下来。然后从同一个页面启动一个专用 Web Worker,让它从自己的 navigator 读取同样的属性,并把结果发回页面。

逐字段比较这两份记录,它们应该完全相同。出现差异通常意味着某个值是由页面脚本设置的,或者 worker 读取的来源与页面不同。专用 worker 最容易测试,共享 worker 和 service worker 也可以加入同一轮比较。

Client Hints 请求头

接下来,把 getHighEntropyValues() 的输出与服务器收到的内容进行比较。该方法会返回你所列出的值的 promise,例如平台版本、架构、位数和完整版本列表。在服务器一侧,熵更高的请求头只有在先前的响应包含了请求它们的 Accept-CH 头之后才会到达。因此测试页面需要一个由你控制的服务器,它发送 Accept-CH 并记录所收到的请求头。

把 Sec-CH-UA 和 Sec-CH-UA-Platform 中的品牌列表和平台与 userAgentData 对象比较,再把平台版本和架构请求头与高熵结果比较。它们都应该描述与 User-Agent 字符串相同的浏览器和平台。

重复会话

关闭浏览器,用相同的配置文件和相同的配置启动新会话,再运行同样的检查。描述平台和硬件的值在每个会话中都应该相同。如果你想确认这些值会一起变化,可以再用另一个配置文件运行一个会话,并检查每个视图是否同时切换到新的设备描述。

比较期间保持浏览器版本和配置文件版本不变。版本变化会合理地改变品牌和版本值,把这种变化混入比较会掩盖你要找的结果。

完整的检查清单,按顺序如下:

  1. 记录主线程上的 navigator 值。
  2. 在 Web Worker 内记录相同的值,并逐字段比较。
  3. 请求高熵值,并与 userAgentData 的品牌列表和平台比较。
  4. 在你控制的服务器上捕获 Client Hints 请求头,并与 JavaScript 值比较。
  5. 用相同的配置文件在全新会话中重复全部步骤,并比较记录。
  6. 每次只改一项设置,并在每次改动后重复比较。

把第一个不一致当作发现。记下是哪两个视图不一致,然后回到配置本身,而不是在上面再叠加一个覆盖,因为每多一个覆盖,就多一个可能不一致的位置。

使用配置文件启动

使用 BotBrowser 时,navigator 的值来自已加载的配置文件,所以最简单的启动方式是一个配置文件加一个临时的用户数据目录:

chrome --bot-profile="path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

BotBrowser 文档把配置文件描述为身份、硬件、区域设置、网络和媒体设备值的来源,并说明 JavaScript 值、HTTP 请求头和 Worker 上下文都从中返回一致的值。先用这个基线启动并运行上面的检查,再添加其他参数。没有配置文件时,navigator 的值反映的是你实际运行的这台机器。

语言和区域设置可以通过 --bot-languages 和 --bot-locale 显式设置。对于网络信息,--bot-network-info-override 控制 navigator.connection 的值以及匹配的 Client Hints 请求头,文档说明由此产生的策略作用于 BrowserContext。请在创建该上下文的第一个页面之前设置它,这样上下文中的每个页面和 worker 看到的值都相同。

chrome --bot-profile="path/to/profile.enc" \
       --bot-languages="en-US,en" \
       --bot-locale="en-US" \
       --bot-network-info-override \
       --user-data-dir="$(mktemp -d)"

与其在某个配置文件之上覆盖单个值,不如选择另一个配置文件。配置文件的构造使相关的值彼此一致,而改动其中一个,比如核心数,会让与它相关的值保持不变。自定义 User-Agent 控制、语言和区域设置参数,以及某些身份参数(例如浏览器品牌和版本)在文档中受许可级别限制,所以在围绕它们做计划之前,请先确认你的许可包含什么。

BotBrowser 控制什么,不控制什么

核对一致性时固定比较三个表面:主线程读 navigator.hardwareConcurrency、navigator.deviceMemory、navigator.userAgentData.brands;在 new Worker() 里读同一组属性;再让服务端回显 Sec-CH-UA 与 Sec-CH-UA-Platform 请求头。三处取值不同,通常是上下文创建顺序或 worker 启动时机的问题。BotBrowser 不能阻止网站收集 navigator 值或把它们与其他信号关联起来,不能保证配置文件与真实硬件或网站的预期相符,也不能替代网络、账户或行为上的一致性;自定义 User-Agent 控制取决于许可级别。

这些限制值得说清楚,因为一组自洽的 navigator 值只回答了网站可能提出的其中一个问题。

  • 网站仍然可以读取 navigator 值,并把它们与 canvas、WebGL、字体和屏幕信号组合起来。这些表面各自都需要单独检查。
  • 配置文件描述的是一台设备,它本身并不是那台设备。网站可能对硬件或软件有任何配置文件都无法预先知道的预期。
  • 网络位置、账户历史、信誉和行为是各自独立的输入。一致的 navigator 值不会改变其中任何一个。
  • 文档所述的行为适用于在受支持的环境中加载的有效配置文件,所以请用上面的清单在你自己的配置中验证。

把 navigator 的一致性看作整体一致配置的一部分。清单告诉你这一部分是否成立,但它不能说明某个特定网站会如何对待一个会话。

关于 navigator 值的常见疑问

问:仅凭 hardwareConcurrency 就足以识别某个人吗? 答:不能。它只是众多输入之一。常见的值被许多浏览器共享,罕见的值会进一步缩小范围,但有用的信号来自它与平台、内存、语言和其他表面的组合。

问:为什么 deviceMemory 最高只到 8? 答:这个值只是近似值,基于 Chromium 的浏览器为它设置上限,以限制它对机器的描述精度。其他浏览器系列根本不提供这个属性,所以在那里它不存在是正常的。

问:只改 User-Agent 字符串有用吗? 答:如果 navigator.platform、navigator.userAgentData 或 Client Hints 请求头仍然描述原来的浏览器,就没有用。相关的值需要一起改变。

问:VPN 或隐私浏览会改变 navigator 的值吗? 答:不会。这些值来自浏览器和操作系统,而不是网络路径或浏览模式。

问:为什么要把 Web Worker 与主线程比较? 答:worker 有自己的 navigator。只在页面脚本中改动的值在那里保持不变,所以 worker 是找出缺口最快的地方。

问:BotBrowser 会让配置文件等同于真实硬件吗? 答:不会。配置文件定义浏览器所报告的值,并让它们在各个上下文中保持一致,但它不会把这台机器变成配置文件所描述的设备。

相关主题请参阅 Client Hints、CPU 核心数、时区、区域设置和语言以及网络信息。完整的控制项请见功能页面,你也可以在验证中心自行验证 navigator 的一致性。

实践结论

从一份配置文件开始,而不是从一串覆盖项开始。加载一份配置文件,读取承载大部分信号的七个值:navigator.userAgent、navigator.platform、navigator.vendor、navigator.hardwareConcurrency、navigator.deviceMemory、navigator.languages、navigator.userAgentData,并记录每一个值的返回结果。

  1. 把这份记录与 new Worker('worker.js') 里的同样读取比较;出现差异说明某个值是由页面脚本设置的。
  2. 把 userAgentData.brands 和平台与服务器在发送 Accept-CH 之后收到的 Sec-CH-UA、Sec-CH-UA-Platform 请求头比较。
  3. 用同一份配置文件在全新会话中重复读取;平台和硬件值不应在会话之间变动。
  4. 当两个视图不一致时,替换配置文件而不是替换那个值,因为第二个覆盖项会多出一个可能不一致的位置。

通过这四步的组合是一致的,而一致性正是这个表面所能主张的全部:浏览器在每个位置对同一个问题给出同样的回答,这还说明不了网站会如何对待这次会话。

来源

本文中关于 navigator、Client Hints 和 BotBrowser 的说明所依据的公开资料:

#Navigator#浏览器指纹识别#浏览器身份#隐私

本文属于指南: 什么是浏览器指纹?原理、风险、信号与隐私边界

让 BotBrowser 从研究走向生产

先用这些指南理解模型,再进入跨平台验证、隔离上下文和面向规模化的浏览器部署。