改 User-Agent 只要一个扩展、十秒钟。macOS 上的浏览器可以自称是 Windows 上的 Chrome,请求头也会这么说。问题在于,请求头已经不是网站最先看的地方了。Chrome 现在会发送第二份结构化的自我描述,另外还有十几个属性各自独立地描述这台机器。新故事和旧故事对不上时,网站学到的不是「你在用 Windows」,而是「你的 User-Agent 是假的」。
网站有两种办法知道你是什么浏览器
User-Agent 字符串是老办法:一行文本,作为请求头发送,也通过 navigator.userAgent 暴露给脚本。从 2022 年起,Chrome 对它做了缩减:平台部分被冻结(所有 Windows 版本都是 Windows NT 10.0; Win64; x64,所有 Mac 都是 Macintosh; Intel Mac OS X 10_15_7,Apple 芯片也一样),版本只显示主版本号,比如 Chrome/152.0.0.0。
User-Agent Client Hints 是新办法。Chrome 在每个请求里发送三个低熵提示(带品牌列表的 Sec-CH-UA、Sec-CH-UA-Mobile、Sec-CH-UA-Platform);服务器通过 Accept-CH 索取时,还会发送高熵提示:平台版本、架构、位数、设备型号和完整版本列表。脚本通过 navigator.userAgentData 和 getHighEntropyValues() 也能拿到同样的数据。从字符串里拿掉的细节,都住在这里。
Firefox 和 Safari 没有实现 Client Hints。Firefox 里没有这些提示是正常的,不算不一致;一个没有它们的 Chrome 才算。
不一致从哪里来
| User-Agent 说 | 但是 | 网站的结论 |
|---|---|---|
| Windows | Sec-CH-UA-Platform 说 "macOS" | 字符串被改了,浏览器没有 |
| Chrome 152 | 完整版本列表说是 148 | 有人改了字符串里的数字 |
Chrome/152.0.6812.113 | Chrome 在字符串里只会发 152.0.0.0 | 字符串是手写的 |
| Chrome | navigator.userAgentData 不存在 | 不是 Chrome,或者是被剖开过的 Chrome |
| Windows | navigator.platform 返回 MacIntel | 操作系统的说法是假的 |
| Windows | 字体列表里有 Helvetica Neue,没有 Segoe UI | 操作系统的说法是假的 |
| Windows | WebGL 报告的是 Apple 显卡 | 操作系统的说法是假的 |
第一行是最常见的:扩展改写了请求头和 navigator.userAgent,其他什么都没碰。其余各行是细心的网站会做的交叉检查,大致按成本排序。没有一项需要权限,所有项都在首次加载页面时完成。
其他必须一致的属性
User-Agent 只是同一台机器至少六种描述中的一种,网站会把它们互相比对:
navigator.platform。Win32、MacIntel或Linux x86_64。它早于 Client Hints 出现,至今仍然必须匹配。- 请求头里的 Client Hints 与 JavaScript 里的。 两者都来自浏览器,理应完全相同;不一致说明其中一个被打了补丁。
- 字体。 每个操作系统都自带一组核心字体(Windows 的 Segoe UI,macOS 的 Helvetica Neue 和 San Francisco),页面无需权限就能探测。
- WebGL 渲染器。 显卡字符串会写出厂商,往往还有只存在于某一平台的驱动栈(Windows 的
Direct3D11,macOS 的Metal)。 - 语音合成的声音。
speechSynthesis的声音列表在 Windows 上是 Microsoft 的声音,在 macOS 上是 Apple 的声音。 - 版本年龄。 落后三个主版本的 Chrome 在真实用户里很少见,在几个月前冻结了身份的工具里很常见。
上面每一个属性描述的仍然是真实的机器。被改过的字符串是唯一与它们全部相悖的值,而这是最容易被识破的一种谎言。
怎么检查
- 打开实验室,看 User-Agent 和 Client Hints 两行。实验室会向 Chrome 索取高熵提示,比较请求头里的副本和 JavaScript 里的副本,再把两者和字符串比较。
- 接着看操作系统一行。它把
navigator.platform、字体、显卡和声音列表与 User-Agent 声称的平台交叉核对。 - 如果你用了 User-Agent 扩展,关掉再测一次。不一致应该消失,这说明扩展就是原因。
- 顺便看版本。如果字符串声称了完整的构建号,或者一个很旧的主版本,这本身就是一个标记。
检测你自己的 User-Agent 与 Client Hints
实验室比较 User-Agent 字符串、Client Hints 请求头和 JavaScript 里的值,并检查字体、显卡和平台是否描述同一个操作系统。
怎么修
如果你只需要真实的浏览器看起来正常: 卸掉 User-Agent 扩展。一个保持更新的原版浏览器能通过上面的每一项检查,因为所有值都来自同一个地方。
如果你需要浏览器呈现另一个身份: 改整个身份,而不是改字符串。User-Agent、请求头和 JavaScript 里的 Client Hints、navigator.platform、字体列表、显卡渲染器和声音列表,都必须描述同一个操作系统和同一个浏览器版本,而且版本要随更新保持当前。这是渲染引擎层面的改动,不是改写请求头能做到的。
无论哪种情况: 不要编造完整版本号,不要声称一个被你的字体和显卡反驳的平台,也不要在 Chromium 浏览器里禁用 navigator.userAgentData 来「隐藏」提示。缺席同样是信号。
Mango Browser 怎么处理 User-Agent
Mango 支持 Windows 和 macOS 上的 Chrome/Chromium 与 Firefox 环境,User-Agent 跟随为该环境选择的引擎、版本和操作系统。在 Chromium 环境里,请求头和 JavaScript 中的 Client Hints 共享同一个身份,所以字符串、Sec-CH-UA-Platform、完整版本列表和 navigator.userAgentData 描述的是同一个浏览器。Firefox 环境保持 Firefox 的行为,包括没有 Client Hints,这正是真实 Firefox 的样子。
操作系统身份带动其余部分:字体列表来自该系统自己的字体池,WebGL 渲染器来自该系统的真实显卡池,声音列表跟随系统,navigator.platform 和屏幕数值与之相配。给一个环境换操作系统,这些值会一起改变,这也是让本文里的交叉检查始终一致的唯一办法。