CSS 指纹:样式表如何追踪你
CSS 媒体查询(如 color-gamut 和 prefers-color-scheme)如何产生指纹信号,以及如何让 CSS 与 JavaScript 报告的值保持一致。
BotBrowser Team
人们通常把 CSS 看作样式语言,而不是追踪面。然而,媒体查询可以让样式表向浏览器询问屏幕、用户设置和可用输入设备的情况,而且不同用户群体得到的答案各不相同。prefers-color-scheme、prefers-reduced-motion、color-gamut、resolution、pointer 和 hover 等特性,各自都会暴露一小部分设备配置。样式表无需一行 JavaScript 就能读取这些信息,因此以脚本为重点的隐私工具有时会忽略这条路径。
观看视频
这段英文视频讲解样式表如何在不运行脚本的情况下把访问者的深色模式设置报告给服务器,并在本地实验页面和两台机器上的官方 Chrome 中,将样式表的答案与脚本读到的值进行对比。检查不使用 BotBrowser 配置文件,也不证明身份、匿名性、访问结果或远程网站的判定。音轨为英文。
真正有用的问题不是某一个特性能否识别一个人。它做不到。问题在于页面通过 CSS 看到的值是否与通过 JavaScript 看到的值一致,以及对你想呈现的配置来说,这些值是否保持稳定。下面各节依次介绍相关的媒体特性、条件加载如何在没有脚本的情况下暴露它们、局部防护为何留有缺口、如何检查一致性、网站作者可以做什么,以及 BotBrowser 覆盖什么、不覆盖什么。
BotBrowser 可以让 CSS 媒体查询及相关显示值与已加载的配置文件保持一致,包括显式指定的配色方案和由配置文件提供的屏幕值。它不能控制网站样式表请求或记录什么,不能让会话匿名,也不会替代无障碍设置。因此,这里的重点是配置文件的一致性及其限制,而不是匿名承诺。
摘要
CSS 媒体查询会暴露关于屏幕、偏好和输入设备的低熵值,条件资源加载还可以在没有 JavaScript 的情况下泄露这些值。让 CSS 和 JavaScript 使用同一个配置文件来源,重复测试多个会话,并把 BotBrowser 的对齐能力当作一致性辅助,而不是匿名保证。
内容导航
- 观看视频
- 哪些媒体特性会带来信号
- 样式表如何在没有 JavaScript 的情况下读取值
- 为什么常见防护留有缺口
- 检查单个配置文件的一致性
- 网站作者可以做什么
- BotBrowser 覆盖什么,不覆盖什么
哪些媒体特性会带来信号
CSS 媒体查询检测页面所在显示环境的条件。对指纹识别最重要的特性可分为四组。
- 显示。
resolution、color、color-index、color-gamut和monochrome描述像素密度、色深以及屏幕能显示的色彩范围。 - 偏好。
prefers-color-scheme、prefers-reduced-motion、prefers-contrast、prefers-reduced-transparency和forced-colors暴露在操作系统或浏览器设置中做出的选择。 - 视口与屏幕。
width、height、aspect-ratio和orientation描述窗口,较早的device-width和device-height则描述屏幕。 - 交互。
hover、any-hover、pointer和any-pointer告诉页面主要输入设备能否悬停,以及它的精度如何。
每个值都有具体来源。显示器硬件决定色域、色深和分辨率。操作系统设置决定深色模式、对比度和动效偏好。浏览器配置可以覆盖系统设置,隐私浏览模式也可能改变某些值。设备类型同样重要:触屏设备通常报告 pointer: coarse 和 hover: none,而台式机报告 pointer: fine 和 hover: hover。
这些信息能暴露多少?W3C Media Queries Level 5 规范承认,用户偏好类特性可能促成指纹识别。单个特性的熵很低,通常是一个布尔值或寥寥几个取值,往往只有一到三比特。组合起来,情况就不同了。TU Graz 的一项研究报告称,同时查询超过十五个特性时,仅凭 CSS 媒体特性就能区分大约十分之三的用户,而总熵在有利条件下被估计为 10 到 15 比特。请把这些数字当作已报告的范围,而不是保证,因为它们取决于被测人群以及页面查询了多少个特性。
粗略计算就能看出组合为何重要。七个特性,每个有两三个取值,就已经能产生几百种不同组合。实际出现的组合更少,因为特性之间存在相关性:带粗糙指针的手机几乎都没有悬停能力,而带精确指针的台式机几乎都有。因此有效信息量低于原始乘积,但仍然高于任何单个特性所显示的。
并非所有取值的分量都一样。常见取值,比如标准桌面显示器上的浅色模式,会把访客放进一个很大的群体,网站从中得知的很少。罕见取值,比如启用的 forced-colors 模式、rec2020 色域或降低透明度的请求,会把访客放进小得多的群体。dynamic-range、prefers-reduced-data 和 inverted-colors 等较新的特性带来更多维度,而浏览器每为平台增加一个特性,样式表就多一个可以询问的值。因此,不寻常的组合通常比常见的组合信息量更大,而不一致的组合信息量最大。
样式表如何在没有 JavaScript 的情况下读取值
脚本可以直接用 window.matchMedia() 测试媒体查询,它会报告某个条件当前是否匹配。这是大多数隐私工具所监视的路径。
const dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const dense = window.matchMedia('(min-resolution: 2dppx)').matches;
const wide = window.matchMedia('(color-gamut: p3)').matches;
第二条路径完全不需要脚本。它是条件资源加载:@media 块内的规则引用了某个 URL。
@media (prefers-color-scheme: dark) {
.tracker {
background-image: url('https://stats.example.com/dark');
}
}
@media (prefers-color-scheme: light) {
.tracker {
background-image: url('https://stats.example.com/light');
}
}
浏览器只会请求条件匹配的那张图片,因此服务器可以根据收到的 URL 得知访客的配色方案。用更多条件重复这种写法,一张样式表就能收集许多二元答案:深色或浅色、高密度或标准密度、精确指针或触控指针。由于信息随普通请求传输,完全屏蔽 JavaScript 的人也可能以这种方式被部分识别。
条件还可以组合。像 @media (prefers-color-scheme: dark) and (min-resolution: 2dppx) 这样的规则只在两部分都成立时匹配,因此样式表可以为每种组合请求一个 URL,而不是为每个特性请求一个。这样请求数量保持在较低水平,同时仍能一次按多个特性区分访客,而且两条路径之间的一处不一致,就可能影响许多规则的结果。
这条路径有两个特点值得记住。第一,媒体查询会在环境变化时重新计算,所以在访问中途切换到深色模式会触发新的请求,变化不仅在加载时可见,也会随时间显现。第二,偏好类特性通常由页面的所有框架共享,而视口类特性跟随各框架自身的尺寸,因此文档与嵌入框架之间的值可能出于正当原因而不同。
这也是两条路径必须一致的原因。样式表和脚本读取的是同一个环境,网站可以比较两者各自的报告。如果 matchMedia('(prefers-color-scheme: dark)') 说的是一回事,而样式表加载的资源说的是另一回事,或者 screen.colorDepth 和 devicePixelRatio 与 CSS 的 color 和 resolution 特性不符,那么不一致本身就是一个信号,无论各个取值是什么。
媒体查询并不是 CSS 中唯一反映平台的部分。@supports 规则会显示浏览器实现了哪些属性和值,系统颜色关键字会解析为操作系统主题定义的颜色,而 system-ui 之类的字体关键字会解析为平台字体。样式表可以利用其中任何一项来判断页面由哪种操作系统或浏览器系列生成,出于同样的原因,每一项都必须与其余配置保持一致。如果配置文件描述的是一个平台,而系统颜色或系统字体却遵循另一个平台,产生的不一致,与媒体查询和 matchMedia() 不符属于同一类问题。
为什么常见防护留有缺口
几种常见做法只解决了问题的一部分,其余部分依然存在。
- 包装
matchMedia()的扩展会改变脚本看到的内容,但样式表仍然根据真实的显示器和设置来计算@media规则。使用条件加载的页面会从 CSS 得到真实值,从脚本得到被修改的值,于是扩展本身造成了不一致。 - 禁用 CSS 不现实,因为大多数网站要靠它才能正常使用。
- 标准化偏好,例如始终报告浅色模式、不减少动效、使用 sRGB 色域,会缩小差异,但也会形成一个显眼的群体。对每项偏好都报告默认值的浏览器,在设置各不相同的人群中可能反而突出。
- Tor Browser 让其用户的许多值保持一致,形成一个庞大的共享群体,但这个群体本身就能被识别为 Tor Browser。
浏览器内置的抗指纹模式遵循类似的模式。Firefox 提供一项抗指纹设置,会为若干偏好类特性报告固定值,这与标准化的取舍相同:群体内部的差异小得多,但群体可以凭其固定值被识别。这些做法对其目标人群来说都没有错。它们只是回答了与本文不同的问题,而本文关注的是,如何让选定的配置在页面可使用的每条路径上保持一致。
剩下的是一致性要求,而不是隐藏要求。CSS 媒体特性、matchMedia() 以及 screen.colorDepth 和 devicePixelRatio 等相关值,都应来自同一个来源,而且这个来源应描述一台自洽的设备。设备像素比策略解释了为什么密度是最常被比较的值之一,而屏幕与窗口指纹一文则涵盖了与之并列的尺寸类取值。
检查单个配置文件的一致性
一个有用的检查方法,是对同一个配置文件通过两条路径比较同一个特性,然后在之后的会话中再比较一次。
- 配色方案。 打开一个带深色主题的页面,确认实际显示的主题与
matchMedia('(prefers-color-scheme: dark)').matches相互一致,并且与你配置的值一致。 - 分辨率。 比较
window.devicePixelRatio与matchMedia('(min-resolution: 2dppx)')的结果。两者应描述相同的密度。 - 色深与色域。 比较
screen.colorDepth与 CSS 的color特性,并对照配置文件所代表的显示器检查color-gamut。 - 指针和 hover。 确认
pointer与hover符合配置文件声明的主输入设备,而它并不总是外形:手机配置文件通常报告 coarse 且无 hover,而桌面配置文件在声明的主输入是触摸屏时,仍然可以报告 coarse 或没有 hover。 - 稳定性。 使用同一个配置文件在多个会话中重复相同的检查。每次的值都应完全相同。
样式表路径和脚本路径都要测试。一个本地小页面,让 @media 规则改变某个可见元素的颜色,就能显示 CSS 看到的内容,而条件加载读取的是相同的值。如果渲染结果与脚本结果不一致,这个差距就是最先要修复的问题,在其他调整之前处理。
深色模式也会改变页面外观。如果你要截图或在多次运行之间比较布局,请显式设置配色方案,使结果保持可比较。未设置的值会跟随宿主机,笔记本电脑和服务器可能不同,这种差异会表现为与页面本身无关的视觉变化。
审查时请包含嵌入框架和打印样式。框架会计算自己的 @media 规则,因此嵌入第二个文档的测试页面可以显示偏好类特性在各框架之间是否一致,而视口类特性则跟随各框架的尺寸。打印样式使用 print 媒体类型,页面可以有意触发它,所以带有 @media print 块的样式表是另一个可以读取显示相关值的地方。把这些当作在其他情境中重复的同一项检查,而不是各自独立的问题。
检查失败时,一次只改一件事。从只加载配置文件的基准启动开始,记录两条路径的值,然后只添加一个选项,比如配色方案或屏幕来源,再次比较。把最终的启动命令记在笔记里,这样之后的会话可以原样重复。如果配色方案与预期不符,请显式设置,而不是依赖默认值。如果特性查询或系统颜色指向的平台与你想要的不同,先确认加载的配置文件描述的确实是你打算使用的平台,因为特性的可用性遵循配置文件。
每一对比较的通过条件
| 要比较的一对 | 通过条件 |
|---|---|
prefers-color-scheme 与 matchMedia | 每次加载两者都报告同一套配色方案 |
devicePixelRatio 与 min-resolution | 查询的切分点与报告的像素比一致 |
screen.colorDepth 与 color | screen.colorDepth 至少是 color 的三倍 |
pointer / hover 与输入设备 | 两者都与配置文件描述的主输入设备一致 |
上面的清单说的是看什么,这张表说的是什么时候那次查看回答了问题。没有写下来的条件,复核者只能凭眼睛比较两个值,然后接受看起来合理的那一对,这正是分歧能活过好几个会话的原因。
在读取任何一个值之前先写下条件,并把结果记成真或假,而不是一对数字。正是布尔行让两个会话可比:同样四行、同样的预期结果,以及一个显示哪一行翻转了的 diff。
注意单位。devicePixelRatio 是像 2 这样的比值,而 min-resolution 接受 dppx 或 dpi,所以 (min-resolution: 2dppx) 与 (min-resolution: 192dpi) 描述的是同一密度,只是写法不同。把比值和 dpi 数值相比,得到的是一次单位错误,而不是配置文件的问题。
也要注意比较方向。(min-resolution: 2dppx) 问的是密度是否至少为二,所以比值正好为 2 的配置文件通过,1.5 则不通过;这是正确结果,不是缺陷。记下你用的确切查询,这样后续会话就不会把改过的查询读成改过的屏幕。
有两行需要特别留意,而且都不是简单的相等关系。CSSOM View 要求 screen.colorDepth 至少是 CSS color 的三倍,因为前者统计的是每像素位数,后者统计的是每颜色分量位数;相等既不被要求也不被保证,所以把二者当作必须一致来比较,会在没有问题的地方报告缺陷。另一行是输入设备:Media Queries Level 5 依据主指针设备而不是外形来定义 pointer 与 hover,并明确允许触摸屏成为台式机的主输入,因此桌面配置文件报告 pointer: coarse 或 hover: none 仍然可以自洽。色彩管理讨论了相邻的情形:色域声明超出了它所描述的显示器。
网站作者可以做什么
网站作者在其中也有自己的责任。@media 媒体查询的存在是为了让呈现方式能够自适应,这一目的值得保护。几个习惯可以让它与数据收集保持分离。
- 用媒体特性来改变样式,避免把网络请求与之挂钩。深色模式下的不同背景通常可以用一个资源加 CSS 自定义属性来处理,或使用
<picture>元素,而不必为每个特性准备单独的 URL。 - 把任何由媒体条件决定的资源放在你自己的源站提供。向第三方主机发出的、因显示或偏好而不同的请求,会让该主机获得一个访客从未同意共享的信号。
- 除非确有需要,不要记录访客获取了哪个条件资源。如果要记录,请在隐私声明中说明,并只短期保留数据。
- 尊重人们设定的偏好。减少动效、对比度和强制颜色属于无障碍设置,关于媒体查询偏好与用户控制的文章介绍了如何在尊重它们的同时保留一个可见的外观选择方式。
这些习惯成本很低,并能减少离开页面的显示与偏好数据。
在作者一侧测试很简单。打开浏览器的网络面板,在操作系统中切换配色方案或减少动效设置,观察哪些请求发生了变化。任何随偏好而变的请求都值得再看一眼:问问设计是否需要它,用一个资源能否得到同样的结果,以及接收它的主机是否属于你。每当添加样式表或第三方小组件时,都应重新检查,因为嵌入的组件可能引入网站其余部分从未使用过的条件请求。
无障碍设置在两侧都需要格外小心。减少动效、高对比度和强制颜色之所以存在,是因为有些人没有它们就无法舒适地使用页面。为了与所有人看起来一样而覆盖这些设置的隐私配置,可能让键盘前的那个人更难使用网站,而无视这些设置的网站则直接伤害这些人。当你用来测试的浏览器报告的值与你自己的设置不同时,请记住这种差异只适用于该会话,你真正的偏好仍然属于你日常使用的浏览器。
BotBrowser 覆盖什么,不覆盖什么
BotBrowser 关于 CSS Signal Consistency 的文档介绍了相关选项。基本启动会加载一个配置文件,配色方案和屏幕来源可以显式设置。
chrome --bot-profile="path/to/profile.enc" \
--bot-color-scheme=dark \
--bot-screen=profile
--bot-color-scheme 接受 light 或 dark。--bot-screen=profile 使用配置文件定义的屏幕属性,例如色深和尺寸,而不是宿主显示器的值。这两个设置都是可选的。
请先选择配置文件,因为会话的 CSS 值都来自它。一个配置文件描述一台设备,所以应选择平台和设备类型与你的工作相符的那个,并用上面的检查确认指针和悬停。之后再更改配色方案或屏幕来源,只是在该配置文件内调整呈现方式,并不会把桌面配置文件变成手机。
BotBrowser 能够让 CSS 媒体值及相关显示值与已加载的配置文件保持一致:配色方案遵循配置文件和 --bot-color-scheme,色深和屏幕尺寸等显示值遵循配置文件,因此 @media 规则与 matchMedia() 报告的是与配置文件一致的同一组值,而不是宿主机的值。这有助于你确认,对给定的配置文件,样式表路径与脚本路径是一致的,并且答案不会在会话之间改变。局限同样重要。BotBrowser 不能控制网站样式表请求或记录什么,不能让会话变得匿名或无法关联,也不会替代你自己的无障碍设置,例如减少动效或对比度。
常见问题
单个 CSS 特性能识别一个人吗?
不能。单个媒体特性通常只提供很少信息;风险来自特性组合,以及 CSS 与 JavaScript 值之间的矛盾。
是否应该统一所有偏好设置?
不一定。固定默认值可以减少差异,却可能形成可识别的群体,并覆盖无障碍设置。
一致性测试时应该记录什么?
记录配置文件、显式启动选项、CSS 结果、脚本结果和会话日期,以便之后重复比较。
实用结论
要进行可重复检查,请选择自洽的配置文件,在需要时显式设置配色方案或屏幕来源,在同一个测试页面中比较 CSS 与 JavaScript,并在后续会话中重复测试。先修复不一致,再调整其他指纹信号;同时记住,BotBrowser 会对齐值,但不会控制网站端的数据收集,也不会提供匿名性。
公开来源
本文属于指南: 什么是浏览器指纹?原理、风险、信号与隐私边界