返回博客
指纹

CSS 信号一致性与 BotBrowser

解释 CSS 媒体查询及 color-depth、prefers-color-scheme 等如何成为指纹信号,以及 BotBrowser 如何保证一致性。

介绍

通常人们把 CSS 视为样式语言,因此容易忽视它作为指纹采集向量的能力。但 CSS 媒体查询会暴露用户显示、偏好和浏览器能力的信息。像 prefers-color-schemeprefers-reduced-motioncolor-gamutresolution`` 和 forced-colors等属性揭示了设备配置细节,这些细节会构成指纹。关键在于,CSS 指纹能在没有 JavaScript 的情况下工作:通过条件资源加载构造的样式表可以在不执行脚本的情况下提取设备信息。本文说明了 CSS 信号如何贡献指纹,以及 BotBrowser 如何确保 CSS 媒体查询、JavaScript 的matchMedia()` 调用和相关 API 返回一致且由配置文件驱动的值。

隐私影响

CSS 指纹在隐私研究界备受关注,因为它运行在与 JavaScript 指纹不同的层级。研究表明,当同时查询 15+ 媒体特性时,仅通过 CSS 媒体特性就可以区分大约 30% 的用户。若将这些与 JavaScript 可访问的属性结合(这些属性应与 CSS 值保持一致),识别率会显著提高。

W3C Media Queries Level 5 规范也承认这些媒体特性的指纹潜力。像 prefers-color-schemeprefers-contrastprefers-reduced-motionprefers-reduced-data 等特性透露了因人而异的偏好。单个特性通常信息熵较低(通常为布尔值或少数取值),但多个特性组合会形成有意义的指纹。

CSS 指纹尤其令人担忧的是它可以在没有 JavaScript 执行的情况下工作。通过 @media 规则有条件地加载的跟踪像素可仅凭请求就向服务器告知用户的显示配置。这意味着即便用户完全屏蔽了 JavaScript,也可能被部分指纹化。

技术背景

与指纹相关的 CSS 媒体特性

CSS 媒体查询用于检测用户环境,下列特性与指纹化最相关:

显示属性:

  • resolution / min-resolution / max-resolution — 屏幕像素密度(dpi 或 dppx)。
  • color — 每个颜色分量的位数。
  • color-index — 设备颜色查找表的颜色数。
  • color-gamut — 近似色域:srgbp3rec2020
  • monochrome — 设备是否为单色及每像素位数。

用户偏好:

  • prefers-color-scheme — 用户首选配色:lightdark
  • prefers-reduced-motion — 是否请求减少动画。
  • prefers-contrast — 是否请求高/低对比度。
  • prefers-reduced-transparency — 是否偏好减少透明度。
  • forced-colors — 浏览器是否处于强制颜色模式(Windows 高对比度)。

视口与显示:

  • width / height — 视口尺寸。
  • device-width / device-height — 屏幕尺寸(已弃用但仍被支持)。
  • aspect-ratio — 视口宽高比。
  • orientation — 方向:portraitlandscape

交互:

  • hover — 主指针设备是否支持悬停。
  • any-hover — 是否有任何设备支持悬停。
  • pointer — 主指针精度:nonecoarsefine
  • 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 媒体特性的值取决于:

  • 硬件。 显示器能力决定色域、色深和分辨率。
  • 操作系统设置。 深色模式、高对比度、减少动画等为 OS 级别偏好。
  • 浏览器配置。 有些浏览器会覆盖系统偏好。隐私/无痕模式可能修改部分值。
  • 设备类型。 触屏设备报告 pointer: coarsehover: none,桌面设备通常报告 pointer: finehover: hover

一致性问题

CSS 值和 JavaScript 值必须一致。如果 window.matchMedia('(prefers-color-scheme: dark)').matches 返回 true,但 screen.colorDepth 指向与 light theme 更匹配的配置,或 CSS 加载的资源与 JavaScript 报告的值相矛盾,那么不一致本身就成了一个指纹信号。

常见保护方法及其局限

浏览器扩展 可以拦截 window.matchMedia(),但无法在样式表级别修改 @media 规则。网页可以用纯 CSS 提取值,而扩展只控制 JS API,从而产生可检测的不一致。

禁用 CSS 会破坏整个网页体验,因此不可行。

标准化偏好(始终报告 light 模式、不减少动画、使用 srgb)会降低指纹,但也会形成罕见的子集。恰好报告默认值的用户并不常见,反而可能突出。

Tor Browser 通过在用户群体间标准化很多 CSS 值降低指纹,但同时会形成可识别的 Tor 指纹。

挑战在于保证 CSS 媒体特性、matchMedia() 和相关 DOM 属性(如 screen.colorDepth)都从同一来源返回受控值。

BotBrowser 的引擎级方法

BotBrowser 在 Chromium 渲染引擎层控制 CSS 媒体特性,确保 CSS 与 JavaScript API 间的一致性。

统一信号源

加载指纹配置文件后,所有显示和偏好相关的值均来自该配置:

  • 配色方案--bot-config-color-scheme 控制,CSS @media (prefers-color-scheme: ...)matchMedia() 返回相同值。
  • 屏幕尺寸 源自配置文件的显示设置,@media (width: ...)screen.width 保持一致。
  • 设备像素比 由配置文件控制,影响 @media (resolution: ...)devicePixelRatio
  • 色深 由配置控制,影响 @media (color: ...)screen.colorDepth
  • 指针与悬停能力 与设备类型相匹配,桌面配置会报告 pointer: finehover: hover,移动配置会报告 pointer: coarsehover: none

CSS 与 JavaScript 对齐

BotBrowser 保证 CSS @media 与 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 指纹(基于资源加载)也会返回与配置一致的值。@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');

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);

验证

CSS-JS 一致性。 对每个媒体特性比较 CSS 评估结果与 JavaScript 报告结果。matchMedia('(prefers-color-scheme: dark)').matches 应与渲染行为一致。

配色渲染。 访问支持深色模式的网站,渲染应与 --bot-config-color-scheme 设置相符。

DPI 一致性。 比较 window.devicePixelRatiomatchMedia('(resolution: 2dppx)').matches,两者应一致。

pointer 与 hover。 验证 matchMedia('(pointer: fine)').matches 是否符合你的配置文件(桌面 vs 移动)。

会话间稳定性。 使用同一配置文件在多次会话中运行相同的媒体查询检查,所有值应保持一致。

最佳实践

  • 显式设置配色方案。 使用 --bot-config-color-scheme=lightdark 控制偏好,避免歧义。
  • 将 pointer/hover 与设备类型匹配。 桌面应报告 pointer: finehover: hover,移动报告 pointer: coarsehover: none
  • 使用配置驱动的显示设置。 --bot-config-screen=profile--bot-config-window=profile 确保尺寸相关的媒体查询匹配配置文件。
  • 用 CSS 与 JavaScript 双重测试。 通过 @media 渲染检查样式并使用 matchMedia() 验证 API 值的一致性。
  • 注意深色模式截图影响。 若需比较视觉输出,确保配色方案一致。

常见问题

问:网站能否在没有 JavaScript 的情况下仅用 CSS 指纹我? 答:能。条件 CSS 资源加载可以在没有任何 JavaScript 的情况下提取媒体特性。BotBrowser 在引擎层控制媒体评估,因此即使是纯 CSS 探测也会使用受控值。

问:prefers-reduced-motion 会影响网页动画吗? 答:会。当 prefers-reduced-motion: reduce 生效时,良好设计的网站会减少或禁用动画。BotBrowser 配置文件会基于采集设备设置此偏好。

问:CSS 媒体特性能提供多少信息熵? 答:每个特性通常提供 1-3 比特(例如配色方案二值,pointer 三值)。当 15+ 个特性组合时,总体信息量可达 10-15 比特(在有利条件下)。

问:BotBrowser 是否处理 forced-colors 答:是的。forced-colors(Windows 高对比度模式)由配置文件控制,标准配置通常报告 forced-colors: none

问:dynamic-range 媒体特性如何? 答:dynamic-rangestandardhigh)表示 HDR 能力,配置文件包含此信息以反映采集设备能力。

问:iframe 中的媒体特性是否一致? 答:是的。BotBrowser 的引擎级控制适用于所有渲染上下文,包括同源与跨域 iframe。

总结

CSS 媒体特性提供了一个独立于 JavaScript 的指纹化面,其能暴露显示配置、用户偏好和交互能力。BotBrowser 在 Chromium 引擎层控制所有 CSS 媒体特性,确保 @media 规则、matchMedia() 查询和相关 DOM 属性都返回由配置文件驱动的一致值。通过 --bot-config-color-scheme 与配置驱动的显示设置,BotBrowser 消除了 CSS 观察值与 JavaScript 报告值之间的差距。

相关主题: What is Browser FingerprintingScreen and Window ProtectionFont Fingerprint ProtectionNavigator Property Protection

title: "BotBrowser CSS 信号一致性" description: "BotBrowser 如何通过浏览器引擎层面的配置文件控制,确保 CSS 媒体查询与 JavaScript API 返回匹配的值。" date: "2025-06-18" locale: zh category: fingerprint tags: ["css", "fingerprinting", "privacy", "color-depth", "media-queries"] published: true

隐私风险

CSS 媒体查询暴露设备信息 - 色深、显示分辨率、配色方案偏好、指针类型 - 无需执行 JavaScript。服务器可以纯粹通过样式表加载行为检测这些值,使得这个攻击面难以观察和控制。

关键挑战: CSS 媒体查询结果必须与 JavaScript API 值一致。基于扩展的工具只能修改 JavaScript 返回值,无法影响 CSS 媒体查询的评估,从而产生可检测的差距。

BotBrowser 的解决方案

BotBrowser 在浏览器引擎层面控制 CSS 相关信号。加载配置文件后,浏览器的内部状态被配置为所有 CSS 媒体查询和对应的 JavaScript API 从同一数据源读取。

配置文件驱动

chrome --bot-profile="/path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

这个单一标志设置屏幕属性、色深、设备像素比和媒体特性值。CSS 查询如 (color: 8)(prefers-color-scheme: light)(pointer: fine) 都反映配置文件的目标设备。

CSS 与 JS 无差异

因为 BotBrowser 修改的是底层平台数据而非修补单个 API,CSS 评估结果与 JavaScript 报告之间不存在差距。

验证

加载配置文件后,验证 CSS 和 JavaScript 的一致性:

const { chromium } = require('playwright-core');

const browser = await chromium.launch({
  executablePath: '/path/to/botbrowser/chrome',
  args: [
    '--bot-profile=/path/to/profile.enc',
  ],
  headless: true,
  defaultViewport: null,
});

const page = await (await browser.newContext()).newPage();

const results = await page.evaluate(() => {
  const cssColor = matchMedia('(color: 8)').matches;
  const jsDepth = screen.colorDepth;

  const cssWidth = matchMedia(`(device-width: ${screen.width}px)`).matches;

  const cssPointer = matchMedia('(pointer: fine)').matches;
  const touchPoints = navigator.maxTouchPoints;

  return {
    colorConsistent: cssColor === (jsDepth === 24),
    widthConsistent: cssWidth,
    pointerConsistent: cssPointer === (touchPoints === 0),
  };
});

console.log('CSS/JS 一致性:', results);
// 所有值应为 true

关键检查项:

  1. matchMedia('(color: 8)')screen.colorDepth 一致
  2. matchMedia('(device-width: Xpx)')screen.width 一致
  3. matchMedia('(pointer: fine)')navigator.maxTouchPoints 一致

快速开始

  1. GitHub 下载 BotBrowser
  2. 使用 --bot-profile 加载指纹配置文件
  3. 在 Playwright 或 Puppeteer 中设置 defaultViewport: null 让配置文件控制尺寸
  4. 使用 DevTools 或测试工具验证 CSS 和 JavaScript 一致性
#css#fingerprinting#media-queries#privacy