BreakHub
知识库浏览器指纹

User-Agent 的退化与 Client Hints

User-Agent 字符串背了三十年历史包袱,Chrome 已经把它冻结成一个几乎不含信息的模板;接替它的是 Client Hints。这篇讲清楚两者各自暴露什么,以及网站如何用特性探测识破一个改过 UA 的浏览器。

5 分钟阅读 · 发布于 2026年9月10日

随便打开一个 Chrome,看一眼 navigator.userAgent,你会看到一串自相矛盾的东西:开头是 Mozilla/5.0,中间写着 AppleWebKit 和 "KHTML, like Gecko",结尾还带个 Safari/537.36。一个 Chrome 在自称 Netscape、Safari 和 Firefox 的亲戚。这不是 bug,而是三十年兼容性妥协叠出来的化石。这串化石怎么来的、为什么正在被冻结、网站现在靠什么认浏览器,这三件事弄清了,本站指纹报告里"浏览器"一栏也就好懂了。

一串谁都不信的字符串

User-Agent 最初的用意很单纯:告诉服务器"我是什么软件",方便服务器按能力下发内容。问题出在九十年代,网站开始按 UA 里有没有 "Mozilla" 来决定给不给高级版页面,于是 IE 也自称 Mozilla,后来者一个接一个照抄。Chrome 出现时为了让所有按 "Safari" 或 "Gecko" 判断的站点都正常工作,干脆把这些词全塞进去。结果是:UA 里几乎每个 token 都在撒谎,真正有用的只有末尾的 Chrome/1xx 和括号里的平台信息。

更麻烦的是,UA 顺手把操作系统版本、CPU 架构、Android 设备型号都写了进去。这些字段对绝大多数网站毫无用处,对追踪者却是免费的熵。

Chrome 的 UA reduction

大约从 2022 年到 2023 年,Chrome 分阶段把 UA 字符串"缩水"了。现在的 Chrome UA 大致是这样的:Windows 一律显示 Windows NT 10.0; Win64; x64,不管你实际跑的是 Win10 还是 Win11;macOS 固定为 Intel Mac OS X 10_15_7,哪怕是 Apple Silicon 机器;Android 版本冻结在 Android 10,设备型号统一替换成字母 K;Chrome 版本号只保留主版本,次版本和补丁号归零,像 Chrome/128.0.0.0

也就是说,今天一个 Chrome UA 能告诉你的只剩三件事:大致的平台、是不是移动端、Chrome 主版本号。这是有意为之——把 UA 降到低熵,然后把细节搬到一个需要"开口要"的新机制里。

Client Hints:把信息拆成默认给的和要来的

Client Hints(UA-CH)就是那个新机制,它把浏览器信息分成两档。

低熵的三项随每个请求默认发送:Sec-CH-UA(品牌和主版本,比如 "Chromium";v="128", "Google Chrome";v="128")、Sec-CH-UA-Mobile?0?1)、Sec-CH-UA-Platform"Windows""macOS""Android" 之类)。这三项加起来暴露的信息和缩水后的 UA 差不多,设计上就是等价替代。

高熵项默认不发,包括 Sec-CH-UA-Platform-Version(能区分 Win10 和 Win11)、Sec-CH-UA-ArchSec-CH-UA-BitnessSec-CH-UA-Model(真实设备型号)、Sec-CH-UA-Full-Version-List(完整版本号)、Sec-CH-UA-WoW64。服务器要想拿到,得在响应头里写 Accept-CH: Sec-CH-UA-Platform-Version, Sec-CH-UA-Model,浏览器在后续请求里才会附上;页面脚本也可以直接调 navigator.userAgentData.getHighEntropyValues(['platformVersion', 'model']),返回一个 Promise。

这里有个常见误解要说清楚:"需要授权"不是指弹窗让你点同意。它是站点声明式的——网站说我要,浏览器就给,用户不会被问。它的价值在于默认不发:被动收集的请求头里没有这些字段,想拿必须主动索要,而主动索要是可以被审计和限制的(比如 Chrome 的隐私沙盒政策、企业策略)。另外注意,截至写作时 Firefox 和 Safari 都没有实现 UA-CH,它们的 UA 也没有像 Chrome 那样大幅冻结,所以"Client Hints 取代 UA"目前主要是 Chromium 系的故事。

那个奇怪的 "Not A(Brand"

Sec-CH-UA 时你会发现品牌列表里混着一个像乱码的东西,比如 "Not A(Brand";v="99""Not/A)Brand";v="8"。这叫 GREASE,故意加进去的噪音。目的很实际:如果网站开发者写了 if (brands[0] === "Chromium") 这种硬编码,将来任何新品牌或顺序变化都会把网站搞坏,就像当年的 "Mozilla" 一样。所以 Chrome 在品牌列表里随机放一个胡编的品牌、随机调整顺序,从第一天起就逼着所有人写健壮的解析代码。

内核检测:UA 说是什么不重要,行为像什么才重要

改 UA 太容易了——扩展、代理、反检测浏览器的设置页都能一键换。所以任何认真的风控都不看 UA 说了什么,而是看浏览器做了什么。这就是内核检测(engine detection)的思路。

几类特征探测很典型:

  • window.chrome 对象只在 Chromium 系存在;旧版 Firefox 有 InstallTrigger(新版已移除,所以这条正在失效);Safari 有 ApplePayErrorsafari.pushNotification 之类只有 WebKit 才有的对象。
  • CSS 支持面:CSS.supports('-moz-appearance', 'none') 为真基本就是 Gecko;-webkit- 系列属性在 Blink 和 WebKit 都有,但具体差异集合不同。
  • navigator.userAgentData 存在与否本身就是一个信号:一个自称 Chrome 128 的浏览器如果没有这个对象,要么是 Firefox 改的 UA,要么是被扩展删掉了。
  • 错误堆栈格式:V8、SpiderMonkey、JavaScriptCore 生成的 Error().stack 文本格式完全不同,改 UA 改不了这个。

确认了引擎,下一步是估计真实版本。方法很像 Chrome Platform Status 那张表:每个 Chromium 版本引入了哪些 API 是公开的——某个版本加了 Array.prototype.toSorted,另一个版本加了 navigator.scheduling,再一个加了某个 CSS 选择器。逐个探测这些特性,能把真实 Chromium 版本框到一个不大的区间。如果 UA 说自己是 Chrome 128,但 Chrome 115 才有的 API 都不存在,那 UA 就是改过的。

本站的 User-Agent 页面还做一件更简单的事:把 HTTP 请求头里的 UA 和 JS 里的 navigator.userAgent 摆在一起。正常情况下两者一模一样。如果不一样,几乎只有两种解释——中间有代理改写了请求头,或者某个扩展篡改了 JS 层。这一项在本站的真实性评分里扣分最重,因为它几乎没有误报。

不一致会怎样

改了 UA 却没改内核,实际后果比想象的多,而且大多对你不利。

风控系统会把"UA 声称 vs 特性实测"的矛盾当作伪造环境的强信号,轻则多弹验证码,重则账号被标记。网站的兼容逻辑会走错分支:UA 说是新版,网站就下发新语法的脚本,老内核跑不动直接白屏;反过来 UA 装老版本,网站又会给你降级的功能。还有安全层面的——浏览器更新提示、某些站点按版本启用的安全特性,都依赖 UA 里的版本号,报错了版本就等于自己关掉了这些提醒。

改 UA 是最容易做、也最容易被识破的伪装。要么整套环境一起改,要么干脆别动。

常见问题

Client Hints 是不是比 UA 更侵犯隐私? 高熵项确实能拿到比缩水 UA 更多的信息,比如真实的 Windows 版本和设备型号。但这些以前就在 UA 里免费送,现在至少得开口要,而且只有 Chromium 系支持。总体上是从"人人可见"变成"要了才给",算是进步,只是不要以为它是隐私保护功能。

Firefox 和 Safari 的 UA 也冻结了吗? 没有 Chrome 那么彻底。Firefox 和 Safari 都做过一些减少 UA 细节的调整(比如 Safari 长期把 macOS 版本固定显示),但都没有实现 UA-CH。它们的 UA 仍然是主要的浏览器标识来源。

用扩展改 UA 能让网站以为我在用别的浏览器吗? 对只看 UA 的站点能,对做特性探测的站点不能。而且一旦被识破,"UA 与内核不符"本身就是比原来 UA 更糟的信号。如果目的是减少被追踪,让浏览器保持默认、和大多数人一样,效果好于任何伪装。