浏览器指纹:为什么无痕模式在它面前形同虚设

一个让人后背发凉的现象

试过这个操作吗——打开浏览器的无痕窗口,访问某个网站,它给你显示了一个访客ID。关掉,再开一个新的无痕窗口,再访问。ID没变。清掉Cookie,换一个User Agent扩展,有时候还是能认出来。

这是怎么做到的?

答案就是浏览器指纹。这玩意儿跟你在网络上的“身份证”差不多,但它不需要你主动提供任何信息。你只要打开网页,浏览器就会悄无声息地暴露一堆特征,组合起来形成一串几乎独一无二的标识符。

GitHub上两万多星的开源项目FingerprintJS,把这套采集流程完整开源了。它Demo页里展示的那个访客ID,背后是几十个维度的特征值拼出来的哈希。

浏览器指纹到底是什么

先理清一个概念。浏览器指纹不是存你电脑上的什么东西——它不像Cookie那样在你本地留个文件。它是靠JS脚本主动去“探查”你浏览器和操作系统暴露出来的各种软硬件特征,收集完以后在服务端或者前端拼成一串指纹

一个特征单独拿出来可能区分度很低。比如屏幕分辨率,2048×1152这个值,成千上万的人都是。但如果你把几十个维度叠在一起,某个维度不同的概率是千分之一,另外一个维度万分之几……通过信息熵计算,当维度的独立性和区分度足够高时,组合出来的指纹在百万级别用户里撞车的可能性可以降到几乎忽略不计。

FingerprintJS的统计:单靠Canvas指纹,10万个用户里只有2个重复。叠加WebGL、字体列表、音频指纹之后,基本上就是一人一码

采集哪些特征?底层怎么干的?

下面我把主要的技术维度拆开讲,包括具体的采样手段和防护为什么难。

1. IP和时区:最直接但也最容易出矛盾

服务端拿到的是TCP连接的对端地址,一般都是用户的公网出口IP。配合IP地理库(像MaxMind、IP2Location或者免费的IP-API),能解析到城市级甚至街道级的位置,还能拿到ISP运营商名称、AS号这些信息。

前端这边,JS可以直接读:

拿到操作系统的时区设置。稍微敏感一点的网站会把这两个值做交叉比对:IP说你在北京(UTC+8),但你系统时区设置的是纽约(UTC-5),这就是一个显著的风险信号,很多反欺诈系统会直接给这个会话加分。

2. Canvas:浏览器指纹的杀手锏

这部分的原理很多人知道个大概,但具体的执行流程很少展开讲。我给你拆开:

网页的JS可以创建一个隐藏的<canvas>元素,调用getContext('2d')拿到绘图上下文,然后开始往上画东西——通常是一段组合了中文、英文、emoji的文字,外加不同颜色、渐变、圆弧。关键步骤来了:调用toDataURL()或者读像素数组,把canvas当前态的渲染结果转成binary dump。

这个dump长什么样?你和我两台机器,同样的代码,画出来的PNG在肉眼看来没有任何区别。但在二进制层面,因为显卡不同、驱动版本不同、操作系统的字体渲染引擎(Windows的DirectWrite vs macOS的Core Text vs Linux的FreeType)处理抗锯齿、字距、亚像素渲染的策略都不一样,导致即使同一个字,边缘像素的RGBA值也会有微小偏移。

拿到这个dump以后,常规做法不是直接传图片(太大了),而是对它跑一次哈希——MD5或SHA-256都可以——得到一个固定长度的指纹字符串。这就是Canvas指纹。

还有一个变种:不只是drawText,还会利用emoji、某些特定矢量图形(比如贝塞尔曲线),这些图形的抗锯齿行为在不同GPU上差异更大,增强区分能力。

目前这种Canvas指纹是用的最多的一种。

3. WebGL:直接嗅探GPU

WebGL这套API原本是给浏览器做3D渲染用的,用它来做指纹采集的思路和canvas类似,但更狠——它能直接拿到显卡的渲染器字符串。

const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);

这段代码直接把你显卡型号暴露出来了,比如“ANGLE (NVIDIA GeForce RTX 4060)”。

但不止于此。更精细的WebGL指纹采集还会包含:

  • 渲染特定3D场景并读回像素(和Canvas类似,但有光照模型、纹理映射的参与,干扰因素更多)
  • 枚举所有支持的WebGL扩展列表,不同浏览器和GPU的组合扩展列表有差别
  • 查看着色器编译器返回的精度参数,不同厂商的浮点精度实现存在微小差异

这部分的难点在于,你改User Agent改不到这个层次;你用虚拟机,虚拟化的显卡驱动暴露的信息和物理机又不同,反而可能成为另一个特征维度。

4. AudioContext:用声音做指纹

这可能是最反直觉的一种采集方式。原理大致是这样:

AudioContext创建一个离线的音频处理图,生成一段固定频率、固定波形的信号,然后过一个动态压缩器或者比特率处理节点,再把结果波形读出来。

理想情况下,同一套代码输出的波形应该完全一致。但实际操作下来,不同的声卡驱动、不同的浏览器对Web Audio API的底层实现,会在信号的最后几位产生浮点精度的舍入差异。

操作流程:

  1. 创建OscillatorNode,设置频率和波形
  2. 连接到DynamicsCompressorNode(这个节点在不同设备上差异大,是核心)
  3. 最后通过OfflineAudioContext渲染,读取结果buffer
  4. 取buffer的特定位置值做哈希

这个小到人耳完全听不出来的浮点差异,经过哈希之后被放大成了指纹。

5. 字体列表:逐个摸排你系统里的字体资产

系统里装了什么字体,这是一个非常强的区分维度。问题是:浏览器没有API直接列出所有已安装字体。怎么办?

Flash时代可以直接枚举,现在不行了。新的玩法是“边试边猜”:准备一个已知字体列表(可以覆盖几百个中英文字体),然后对每一个字体,用JS创建一个span,设置font-family为候选字体名字,塞一个参考字符串进去,然后通过offsetWidthgetBoundingClientRect去对比,看它渲染出来的宽度和默认fallback字体是否一致。一致就说明这个字体你本地有。

这招虽然笨,但效率比想象的高——几百个字体测下来也就一两百毫秒。不同人的字体集合重合概率极低:设计师装了一堆中文字体库,程序员一堆等宽字体,普通人可能装了某个办公软件带来的一批专用字体。这些组合起来,区分度非常高。

6. 硬件参数与布局指纹

几个直接暴露的API:

  • navigator.hardwareConcurrency:CPU逻辑核心数。AMD的SMT和Intel的超线程都算一个逻辑核心,所以16线程就是16。这个值在主流设备上一共就那么几种(2/4/6/8/12/16/20/24...),但配合其他维度也是加熵的。
  • navigator.deviceMemory:只在Chrome系有,而且被锁死在0.25/0.5/1/2/4/8几个档位,8GB是一个硬上限。隐私考虑。
  • screen.width/heightwindow.innerWidth/innerHeightscreen.colorDepth这些是常规项目。

还有一个容易忽略的点叫元素布局指纹。同一个页面里的某个DOM元素,在不同浏览器内核、不同系统缩放比例、不同设备像素比(DPR)下,getBoundingClientRect()拿到的位置和尺寸可能有亚像素级别的差异。更复杂的做法是利用CSS的一些边界情况(比如某些属性在不同引擎下的默认值差异)来进一步提炼信号。

7. 其他值得注意的低调特征

  • navigator.plugins 和 navigator.mimeTypes:曾经能暴露完整插件列表,现在大部分浏览器已经阉割了,但残留信息仍然有区分度。
  • speechSynthesis.getVoices():语音朗读引擎列表。不同系统的语音包完全不同,MacOS、Windows10、Windows11、Android之间的差异一眼就能看出来。
  • navigator.platformnavigator.vendor等老API,虽然逐步废弃,但仍能收集。
  • window.openerperformance.memory等接口的可用性本身也会暴露浏览器类型和版本信息。

网站收集这些信息到底干什么

有了这套技术,能做的事情就多了。

广告追踪:传统的跨站追踪靠第三方Cookie。Cookie被ban、被用户手动清掉后,浏览器指纹就成了救命稻草。你用一个无痕窗口逛了A站,再打开B站,广告联盟通过指纹把两个会话关联成同一个人,知道你在A站看了什么,就推什么广告到B站给你。全程无登陆。

风控反欺诈:这是另一个重头。电商、社交、金融类网站最怕的不是流量,是一人多号。你注册了50个账号薅羊毛,每个账号的IP可能都不一样(挂了代理),但你的Canvas指纹、字体列表、WebGL渲染器三个维度一碰:“同一个人”。短期批量注册、机器自动发的帖子、恶意刷的评论、撞库登录——在这些场景下浏览器指纹的介入让黑产的模拟成本高出一个量级。

异常行为检测:前面提过,时区和IP不匹配、WebGL信息突然从“N卡”变成“核显”、屏幕分辨率突然跳跃——正常的用户不会出现这些瞬间变化。这种指纹的剧烈改变要么是换设备,要么是被盗号(别人从另一台机器登录),要么是切到了虚拟机,要么是正用指纹浏览器伪装。无论哪种,都值得触发一个二次验证。

有没有办法反制?

有,但不是普通的你我能轻易搞定的。

Tor浏览器的策略:Tor选择了一种极端但非常有效的路线——让所有人的指纹看上去都差不多。它统一窗口大小、限制字体枚举、禁止Canvas数据回读、禁用WebGL、把时区锁成UTC、限制navigator暴露的信息。代价是很多页面的功能和性能会受影响,但在Tor的场景下可以接受。

指纹浏览器:这个方向相反,走的是“定制伪装”路线。每个浏览器实例可以指定不同的Canvas指纹注入种子值、替换WebGL渲染器字符串、伪造字体列表、随机化AudioContext输出。这样一台物理机上开二十个窗口,每个窗口在网站那里都是不同的人。

这类工具底层通常是Chromium内核改造,在渲染管线层面做了hook——比如Canvas的toDataURL在执行前,脚本会在画好的图像上注入一层肉眼看不到的随机噪声,这样每次生成的哈希值就有了差异但又不会被人眼察觉。

但这也有个悖论:如果你的伪装做得不够好,使用了默认参数或者和别人的伪装模板重复,平台反向收集这些“使用了指纹浏览器的共同特征”又能反制。攻防一直在升级。

普通用户能做什么:最少安装一两个不必要的浏览器扩展(减小插件指纹面);避免在无痕和非无痕窗口间混合使用可以关联身份的服务;如果隐私需求高,直接用Tor。如果想在方便和安全之间折中,Firefox的“增强跟踪保护”和隐私浏览模式是一个相对简单的挡箭牌,至少能挡住常规的Canvas和WebGL指纹采集尝试。


浏览器指纹这件事讲到底,其实是一个更根本的问题:浏览器的开放性是把双刃剑。开放API让网页体验变得丰富,同时也让用户无形中暴露得干干净净。我们很难彻底消灭自己的数字指纹,但至少知道它是怎么来的,就能在做选择时有更多底气。


文中白帽技术细节来自FingerprintJS开源代码、Web标准规范以及公开安全研究,不涉及任何未公开的黑产手段。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值