CSS 指纹:样式表如何追踪你
CSS 媒体查询(如 color-depth 和 prefers-color-scheme)如何创建指纹信号,以及如何确保一致的 CSS 身份。
简介
CSS 通常被忽视为指纹追踪向量,因为它主要是一种样式语言。但 CSS 媒体查询暴露了有关用户显示器、偏好设置和浏览器能力的信息。prefers-color-scheme、prefers-reduced-motion、color-gamut、resolution 和 forced-colors 等属性揭示了有助于指纹追踪的设备配置细节。CSS 指纹追踪特别有效的原因在于它无需 JavaScript 即可运行。一个精心构造的样式表可以通过条件资源加载提取设备信息,对基于脚本的隐私工具不可见。本文解释 CSS 信号如何参与指纹追踪,以及 BotBrowser 如何确保 CSS 媒体查询、JavaScript matchMedia() 调用和相关 API 都返回一致的、配置文件驱动的值。
隐私影响
基于 CSS 的指纹追踪在隐私研究社区引起了关注,因为它在不同于 JavaScript 指纹追踪的层级上运行。2021 年格拉茨工业大学的一项研究表明,当同时查询 15 个以上的媒体特性时,仅 CSS 媒体特性就可以区分约 30% 的用户。与 JavaScript 可访问的属性(应与 CSS 值一致)结合使用时,识别率显著提高。
W3C Media Queries Level 5 规范承认了媒体特性的指纹追踪潜力。规范指出,prefers-color-scheme、prefers-contrast、prefers-reduced-motion 和 prefers-reduced-data 等特性各自揭示了在人群中有差异的用户偏好。虽然每个特性单独的熵值很低(通常是布尔值或几个可能的值),但许多特性的组合创建了有意义的指纹。
CSS 指纹追踪尤其令人担忧,因为它可以在不执行 JavaScript 的情况下运行。通过 @media 规则条件加载的追踪像素仅通过请求本身就能告诉服务器用户的显示配置。这意味着即使完全阻止 JavaScript 的用户也可以通过 CSS 被部分追踪。
技术背景
与指纹追踪相关的 CSS 媒体特性
CSS 媒体查询测试关于用户环境的条件。以下特性与指纹追踪最为相关:
显示属性:
resolution/min-resolution/max-resolution- 屏幕像素密度,以 dpi 或 dppx 为单位。color- 每个颜色分量的位数。color-index- 设备颜色查找表中的颜色数量。color-gamut- 近似色域:srgb、p3或rec2020。monochrome- 设备是否为单色及每像素位数。
用户偏好:
prefers-color-scheme- 用户偏好的颜色方案:light或dark。prefers-reduced-motion- 用户是否请求了减少动画。prefers-contrast- 用户是否请求了高对比度或低对比度。prefers-reduced-transparency- 是否偏好减少透明度。forced-colors- 浏览器是否处于强制颜色模式(Windows 高对比度)。
视口和显示:
width/height- 视口尺寸。device-width/device-height- 屏幕尺寸(已弃用但仍受支持)。aspect-ratio- 视口宽高比。orientation- 视口方向:portrait或landscape。
交互:
hover- 主要指向设备是否可以悬停。any-hover- 是否有任何指向设备可以悬停。pointer- 主要指向设备的精度:none、coarse或fine。any-pointer- 任何可用指向设备的精度。
CSS 指纹追踪的工作原理
CSS 指纹追踪可以通过两种方法执行。
基于 JavaScript: 使用 window.matchMedia() 以编程方式测试媒体查询:
const darkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
const highDPI = window.matchMedia('(min-resolution: 2dppx)').matches;
const p3Gamut = window.matchMedia('(color-gamut: p3)').matches;
纯 CSS(无 JavaScript): 使用条件资源加载:
@media (prefers-color-scheme: dark) {
.tracker { background-image: url('https://track.example.com/dark'); }
}
@media (prefers-color-scheme: light) {
.tracker { background-image: url('https://track.example.com/light'); }
}
服务器根据请求的 URL 来确定用户的颜色方案偏好。通过链接多个条件,一个纯 CSS 指纹追踪样式表可以提取数十个二进制信号。
为什么 CSS 信号会变化
CSS 媒体特性值取决于:
- 硬件。 显示器能力决定了色域、色深和分辨率。
- 操作系统设置。 深色模式、高对比度、减少动画和辅助功能设置是操作系统级别的偏好。
- 浏览器配置。 一些浏览器覆盖操作系统偏好。隐私浏览模式可能修改某些值。
- 设备类型。 触摸设备报告
pointer: coarse和hover: none。桌面设备报告pointer: fine和hover: hover。
一致性问题
CSS 值和 JavaScript 值必须一致。如果 window.matchMedia('(prefers-color-scheme: dark)').matches 返回 true,但 screen.colorDepth 暗示浅色主题配置,或者 CSS 加载的资源与 JavaScript 报告的值矛盾,这种不一致本身就是一个指纹信号。
常见保护方案及其局限性
浏览器扩展可以拦截 window.matchMedia(),但无法在样式表级别修改 CSS @media 规则。页面可以使用纯 CSS 提取值,而扩展只控制 JavaScript API。这造成了可检测的不一致。
禁用 CSS 会破坏整个 Web。这不是可行的方案。
标准化偏好(始终报告浅色模式、无减少动画、srgb 色域)减少了指纹,但创建了一个独特的子集。一个对每个偏好都报告"默认"值的用户并不常见,可能反而更突出。
使用 Tor Browser 在所有用户之间标准化了许多 CSS 值。但这创建了一个已知的 Tor Browser 指纹,本身是可识别的。
挑战在于确保 CSS 媒体特性、JavaScript matchMedia() 查询和相关 DOM 属性(如 screen.colorDepth)都从单一来源返回一致的、受控的值。
BotBrowser 的引擎级方案
BotBrowser 在 Chromium 渲染引擎级别控制 CSS 媒体特性,确保 CSS 和 JavaScript API 之间的一致性。
统一的信号来源
当加载指纹配置文件时,所有与显示相关和偏好相关的值都来自配置文件:
- 颜色方案由
--bot-config-color-scheme控制。CSS@media (prefers-color-scheme: ...)规则和 JavaScriptmatchMedia()查询返回相同的值。 - 屏幕尺寸来自配置文件的显示配置。CSS
@media (width: ...)和 JavaScriptscreen.width一致。 - 设备像素比来自配置文件,同时控制 CSS
@media (resolution: ...)和 JavaScriptdevicePixelRatio。 - 色深来自配置文件,控制 CSS
@media (color: ...)和 JavaScriptscreen.colorDepth。 - 指针和悬停能力匹配配置的设备类型。桌面配置文件报告
pointer: fine和hover: hover。移动配置文件报告pointer: coarse和hover: none。
CSS-JavaScript 对齐
BotBrowser 保证 CSS @media 规则看到的内容与 JavaScript API 报告的内容之间没有差距。这是因为 CSS 媒体评估和 JavaScript 属性访问都从相同的引擎级配置读取,而该配置由配置文件设置。
这适用于:
@media (prefers-color-scheme: dark)与matchMedia('(prefers-color-scheme: dark)')@media (min-resolution: 2dppx)与window.devicePixelRatio@media (pointer: fine)与matchMedia('(pointer: fine)')@media (color-gamut: p3)与matchMedia('(color-gamut: p3)')
条件资源加载
由于 CSS 媒体特性在引擎级别受控,即使纯 CSS 指纹追踪(通过条件资源加载)也返回与配置文件一致的值。基于颜色方案加载追踪像素的 @media 规则看到的是配置文件的颜色方案,而非你的系统偏好。
跨上下文一致性
CSS 媒体特性在以下上下文中保持一致:
- 主文档样式表
- Shadow DOM 样式表
- 来自任何 JavaScript 上下文的
matchMedia() - iframe 内容(同源和跨源)
- 打印媒体类型查询
配置和用法
基本配置文件加载
chrome --bot-profile="/path/to/profile.enc" \
--user-data-dir="$(mktemp -d)"
CSS 信号从配置文件自动配置。
颜色方案控制
# 强制浅色模式
chrome --bot-profile="/path/to/profile.enc" \
--bot-config-color-scheme=light
# 强制深色模式
chrome --bot-profile="/path/to/profile.enc" \
--bot-config-color-scheme=dark
显示配置
# 使用配置文件的屏幕和窗口设置
chrome --bot-profile="/path/to/profile.enc" \
--bot-config-screen=profile \
--bot-config-window=profile
# 禁用设备缩放因子覆盖
chrome --bot-profile="/path/to/profile.enc" \
--bot-config-disable-device-scale-factor=true
Playwright 集成
const { chromium } = require('playwright');
const browser = await chromium.launch({
executablePath: '/path/to/botbrowser/chrome',
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-config-color-scheme=light',
'--bot-config-screen=profile',
'--bot-config-window=profile'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
// 验证 CSS 信号一致性
const signals = await page.evaluate(() => ({
darkMode: window.matchMedia('(prefers-color-scheme: dark)').matches,
highDPI: window.matchMedia('(min-resolution: 2dppx)').matches,
finePointer: window.matchMedia('(pointer: fine)').matches,
hoverCapable: window.matchMedia('(hover: hover)').matches,
colorDepth: screen.colorDepth,
devicePixelRatio: window.devicePixelRatio
}));
console.log(signals);
Puppeteer 集成
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
executablePath: '/path/to/botbrowser/chrome',
defaultViewport: null,
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-config-color-scheme=dark'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
验证
CSS-JavaScript 一致性。 对于每个媒体特性,比较 CSS 评估的值与 JavaScript 报告的值。matchMedia('(prefers-color-scheme: dark)').matches 应与你观察到的渲染行为一致。
颜色方案渲染。 访问一个支持深色模式的网站。渲染应与 --bot-config-color-scheme 设置(light 或 dark)一致。
DPI 一致性。 比较 window.devicePixelRatio 与 matchMedia('(resolution: 2dppx)').matches。它们应该一致。
指针和悬停。 验证 matchMedia('(pointer: fine)').matches 为你的配置文件(桌面或移动)返回预期值。
跨会话稳定性。 使用同一配置文件跨多个会话运行相同的媒体查询检查。所有值应相同。
最佳实践
- 显式设置颜色方案。 使用
--bot-config-color-scheme=light或dark控制偏好。默认值来自配置文件,但显式控制避免了歧义。 - 将指针/悬停与设备类型匹配。 桌面配置文件应报告精细指针和悬停能力。移动配置文件应报告粗略指针和无悬停。BotBrowser 配置文件自动处理此项。
- 使用配置文件驱动的显示设置。
--bot-config-screen=profile和--bot-config-window=profile确保所有与尺寸相关的媒体查询匹配配置文件。 - 同时使用 CSS 和 JavaScript 进行测试。 验证媒体特性通过
@media规则(通过检查渲染样式)和matchMedia()API 调用返回一致的值。 - 考虑深色模式对渲染的影响。 网站在深色模式下渲染不同。如果你的用例涉及截图或视觉比较,确保颜色方案设置一致。
常见问题
问:网站能否仅通过 CSS 而不用 JavaScript 对我进行指纹追踪?
答:可以。条件 CSS 资源加载(触发 URL 请求的 @media 规则)可以在没有任何 JavaScript 的情况下提取媒体特性值。BotBrowser 可以防护这种情况,因为 CSS 媒体评估使用与 JavaScript API 相同的受控值。
问:prefers-reduced-motion 会影响 Web 动画吗?
答:是的。当 prefers-reduced-motion: reduce 生效时,设计良好的网站会禁用或简化动画。BotBrowser 配置文件根据配置设备的配置来设置此偏好。
问:CSS 媒体特性提供多少比特的熵值? 答:每个特性通常提供 1-3 比特(例如,颜色方案的 light/dark,指针的 fine/coarse/none)。跨 15 个以上特性的组合,总计可在有利条件下达到 10-15 比特。
问:BotBrowser 是否处理 forced-colors 媒体特性?
答:是的。forced-colors 特性(指示 Windows 高对比度模式)由配置文件控制。标准配置文件报告 forced-colors: none。
问:dynamic-range 媒体特性呢?
答:dynamic-range 特性(standard 或 high)指示 HDR 显示能力。BotBrowser 配置文件根据配置设备包含此信息。
问:CSS 媒体特性在 iframe 中一致吗? 答:是的。BotBrowser 的引擎级控制适用于所有渲染上下文,包括同源和跨源 iframe。媒体特性在所有框架中保持一致。
总结
CSS 媒体特性提供了一个独立于 JavaScript 运行的指纹追踪面,并揭示了显示配置、用户偏好和交互能力。BotBrowser 在 Chromium 引擎级别控制所有 CSS 媒体特性,确保 @media 规则、matchMedia() 查询和相关 DOM 属性都返回从加载的配置文件派生的一致值。通过 --bot-config-color-scheme 进行偏好控制和配置文件驱动的显示设置,BotBrowser 消除了 CSS 观察到的值与 JavaScript 报告的值之间的差距。
相关主题请参阅什么是浏览器指纹、屏幕和窗口保护、字体指纹保护以及Navigator 属性保护。