尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

反指纹浏览器camofox:基于Firefox的伪装与隐私强化实践

反指纹浏览器camofox:基于Firefox的伪装与隐私强化实践 1. 从一次隐身失败实验说起camofox 的立项背景Chrome 隐身模式被各大网站反手识破的尴尬想必不少人都经历过。我一度以为打开无痕窗口就已经隐形了直到某天在浏览器指纹检测页上做了个对比实验普通窗口和无痕窗口跑出来的指纹哈希值居然只有不到 2% 的差异连显卡型号、字体列表、Canvas 绘制的细微噪点都一模一样。那一刻我意识到我们以为的隐身在追踪系统眼里不过是换了个干净的马甲身体特征一点没变。camofox-browser 就是在这个背景下立项的。它是一款以 Firefox 为底座的隐私强化浏览器项目代号取自 Camouflage伪装与 Firefox 的合成——目标不是让用户在网络世界里消失而是把浏览器伪装成一台每次访问都参数漂移的机器让指纹追踪体系拿回去的每一份数据都不可靠、不可关联、不可用于跨站画像。如果你正在做同类项目或者只是想在现有浏览器上把反指纹做到接近极限这篇文章应该都能给你一些可落地的参考。我会从威胁分析讲起再到架构设计、核心参数拆解、实测数据和踩坑记录全程不聊玄乎的绝对安全只讲我在 camofox-browser 实际开发中验证过的东西。为什么要拿 Firefox 当底座而不是 Chromium这是我立项时纠结最久的问题。当时做了个对比维度Chrome/Chromium 系Firefox/Gecko 系Canvas/WebGL 渲染路径Blink Skia生态太统一Gecko 自有光栅化路径与 Chromium 指纹天然不同内置反指纹机制几乎没有靠第三方扩展privacy.resistFingerprinting开箱即用遥测与数据上报需逐项关更新后可能复活编译期可直接裁掉源码可控性删减版多但核心组件复杂源码结构清晰便于 fork 改造扩展生态丰富但权限审查较松WebExtension 模型成熟风格选项多火狐的 RFPresistFingerprinting机制给了我一个非常高的起点它不光把navigator里的各项属性统一拍平连时间精度、Canvas 读取、窗口尺寸这些底层东西都做了处理。我只需要在它之上补充动态伪装和一致性校验两个能力就能做成 camofox-browser 的核心价值。2. 先搞清楚要防什么浏览器指纹的六大采集面很多做过基础反指纹的人都会犯一个错只改了 User-Agent 就以为完事了。实际上现代指纹追踪系统采集的维度远比你想象得多我把它归成六大类camofox-browser 的每一项配置都是对着这六类逐一设计的。2.1 基础环境指纹不只是 User-AgentUA 只是最表面的一层。请求头里还有Accept-Language、Accept-Encoding、Sec-Fetch-*、Sec-CH-UA这些字段服务器端还会根据 TLS 握手的 JA3 指纹、HTTP/2 的帧顺序、连接特性来推断客户端。换句话说你把 UA 改成任何想要的平台但如果Accept-Language还是默认的一长串中文列表、TLS 指纹还是老版本浏览器的特征反而会露出破绽。camofox 的处理思路是全链路对齐改 UA 时必须同步调整intl.accept_languages、general.useragent.*系列参数并在扩展层对navigator.platform、navigator.vendor等 JS 可读属性做一致化覆盖。2.2 Canvas 与 WebGL 渲染指纹最难骗过的笔迹Canvas 指纹的原理简单说就是让浏览器绘制同一段文字、同一个图形然后读取绘制后的像素数据做哈希。不同显卡、不同驱动、不同光栅化引擎画出来的结果在个别像素上会有微妙差别这个差别就像人的笔迹一样稳定而且普通用户完全感知不到。WebGL 同理WEBGL_debug_renderer_info能直接读出 GPU 型号和驱动的内部字符串这些信息即使你在 UA 里把自己伪装成另一台设备也照样会泄露真实硬件身份。camofox-browser 的选择是默认禁用 WebGL同时对 Canvas 读取接口注入确定性噪声。注意我用的是确定性噪声不是完全随机——完全随机会导致同一个页面内多次读取结果不一致很容易被站点脚本识别为有反指纹机制从而标记你的会话。确定性噪声则保证同一次会话内数值稳定但两台设备之间的差异被抹平。2.3 字体枚举指纹把已安装软件全暴露出去网页里的document.fonts.check()和 Canvas 宽度测量可以暴力探测出系统里装了哪些字体。对于大多数人来说字体列表其实是相当稳定的设备标识——你装过的设计软件、办公套件、特殊字体都会成为指纹的一部分。0.5 秒内就能测出上千种字体的存在与否。这条采集面特别容易被忽略因为大部分反指纹教程压根不提字体。camofox 的做法是在扩展层拦截字体探测逻辑向脚本返回一个固定的字体白名单白名单按最通用的无衬线/衬线字体配置确保任何网站看到的字体列表都是一样的。2.4 设备与环境参数屏幕、时区、并发数、内存屏幕分辨率、DPR、色深、时区偏移、navigator.hardwareConcurrency、navigator.deviceMemory、语言偏好——这些参数单独看没什么但组合起来就是一个相当独特的标识。比如全球 99.9% 的人不在某个特定时区如果你的时区显示那个时区追踪系统就能把你筛出来。这里有个关键点这些参数必须彼此一致。你不能同时声称自己在 Windows 10 1920x1080 屏幕又让navigator.deviceMemory返回一个只在移动端常见的参数。camofox-browser 把这组参数统一在配置中心里管理改一次全链路生效而不是散落在各处各自为政。2.5 WebRTC 与网络层泄露WebRTC 的 STUN 机制会向公网服务器暴露你的内网 IP 和公网 IP即使你挂了代理也拦不住这个通道。这一块是纯网络层面的问题browser 层面的解决方案只有两个彻底禁用 WebRTC或者在偏好设置里把media.peerconnection.enabled关掉。camofox 默认直接关闭media.peerconnection同时关闭了media.navigator.enabled和media.ondevicechange.enabled不让网页枚举麦克风、摄像头等媒体设备。代价是不能在线网页视频通话这是我自己能接受的取舍也建议你在自己的项目中提前想清楚这个权衡。2.6 行为指纹浏览器层的防守盲区行为指纹采集鼠标轨迹、滚动速度、点击间隔、键盘输入节奏这些数据生成在网页层浏览器做任何 UA 伪装都防不了。对 camofox-browser 来说行为指纹是明确的盲区我能做的是尽量降低行为数据与其他指纹维度的关联性——比如固定的时间抖动会让行为数据的时间轴变得不可靠从而降低行为指纹的长期稳定性。这六类采集面整理完之后camofox 的整体防守目标就清晰了不追求每个维度都绝对干净而是追求每个维度的输出对追踪系统来说不稳定、不可关联。这是整个项目的核心哲学。3. 三层伪装架构从编译配置到扩展联动的整体设计确定目标之后我把 camofox-browser 的架构拆成了三层。每一层解决一类问题三层叠加才能覆盖前面提到的六个采集面。3.1 第一层发行层——选稳定的底子编译期裁掉累赘camofox 基于 Firefox ESR 分支构建。选 ESR 而不是 Release 的原因很简单ESR 每 8 周一个主版本、每年一个大版本行为变化少我基于它做的参数调优和扩展适配不用频繁重做。Release 频道六个星期就给你塞一堆新特性每个新特性都可能引入新的指纹暴露面。发行层的具体工作包括用 mozilla-central 的 build 配置禁用MOZ_TELEMETRY_REPORTING、MOZ_DATA_REPORTING等编译开关把遥测从根上裁掉。移除默认搜索引擎、Pocket、账户同步等与浏览器核心功能无关的组件减少请求外发的可能性。设置固定的构建版本号策略避免 UA 里出现 Firefox 120.0.1 这样容易被扫描比对的精确版本组合。如果你不想走源码编译这条路用官方 Firefox ESR 加 profile 层的完整 user.js 也能达到 80% 的效果。两者的差别主要在于遥测和数据上报能不能在编译期彻底移除。3.2 第二层Profile 层——user.js 系统化加固第二层是所有反指纹项目的重头戏user.js。这个文件放在 profile 目录里Firefox 每次启动都会读取并应用里面的参数相当于一套启动即注射的硬编码配置。我在这层做的事包括打开privacy.resistFingerprinting及配套的 letterboxing 窗口混淆。关闭网络预测、预连接、DNS 预取减少因浏览器主动行为产生的流量指纹。禁用定位、通知、后台同步、beacon 发送等容易泄露环境信息的 API。设置统一的intl.accept_languages和时区处理策略。这层的核心原则是凡是能在 browser 内置机制层面解决的绝不动用扩展。因为扩展越多暴露面越大而且部分扩展本身就带着明显的特征某些知名广告拦截扩展的请求模式一眼就能认出来。3.3 第三层扩展层——动态伪装与一致性校验user.js 解决的是静态拍平问题但它有个天然缺陷所有跑这个配置的机器指纹特征是完全相同的。这听起来是好事实则不然——如果一万个人用同一套配置大家共享同一个集体指纹追踪系统可能会把这一万个人当成同一个人来看待那就成了另一种形式的身份合并。所以 camofox-browser 的第三层引入了有限随机性在保持内部一致性的前提下让每台机器、每次安装生成一组随机的身份参数包括 UA 操作系统选择、屏幕分辨率近似值、语言偏好顺序等。这部分用 WebExtension 实现通过privacy.network.webRTCIPHandlingPolicy、privacy.userAgentOverride等接口进行动态控制。三个层面加起来camofox-browser 的工作流就是这样发行层保证底子干净profile 层保证现状统一扩展层给每个用户一个不撞车的独立身份。三层缺一不可而且每一层都有各自维护的参数清单用一套 Python 脚本统一生成避免手工改配置时出现前后矛盾。4. 核心改造落地方案参数、代码与配置逐项拆解架构说完直接上实操。这一节的所有配置都来自 camofox-browser 当前主分支你可以根据自己的需求增删。再强调一次不加解释的参数清单没意义所以我尽量把每个关键项的为什么也写清楚。4.1 user.js 核心参数清单首先是一组与 RFP 直接相关的参数// 开启 RFP统一时间精度、Canvas 噪声、媒体设备枚举等 user_pref(privacy.resistFingerprinting, true); // 开启窗口混淆将窗口尺寸随机调整到最接近的多档预设值 user_pref(privacy.resistFingerprinting.letterboxing, true); // 对已保存的登录态加强隔离 user_pref(privacy.resistFingerprinting.block_mozAddonManager, true);letterboxing这个参数很多人第一次看到会困惑它的作用是把浏览器窗口内容区强制缩放到一组预设的整数尺寸之一比如 1000x700、1365x768 这样。这样当脚本读取innerWidth和innerHeight时不同用户拿到的永远是预设值中的某一个而不是真实分辨率的精确值。代价是页面上会出现一圈信箱式的黑边这是完全可以接受的。第二组是信息泄露通道的关闭清单// 关闭定位 user_pref(geo.enabled, false); user_pref(geo.provider.network.url, ); // 关闭 WebRTC 相关通道 user_pref(media.peerconnection.enabled, false); user_pref(media.peerconnection.ice.default_address_only, true); user_pref(media.ondevicechange.enabled, false); // 关闭蓝牙、USB、电池等设备枚举 user_pref(bluetooth.enabled, false); user_pref(dom.webusb.enabled, false); user_pref(dom.battery.enabled, false); // 关闭 sendBeacon 与后台事件上报 user_pref(beacon.enabled, false); user_pref(dom.broadcastchannel.enabled, false);这里我想多说一句dom.battery.enabled。电量 API 看起来人畜无害但它能暴露电池剩余量百分比而且这个值在长时段内具有相当强的稳定性早年已经被拿来做用户追踪了。这种看似无害的小 API往往是最危险的身份泄漏通道因为没人想到要去关它。第三组是网络主动行为的收敛// 关闭 DNS 预取、预连接、预渲染 user_pref(network.dns.disablePrefetch, true); user_pref(network.prefetch-next, false); user_pref(network.http.speculative-parallel-limit, 0); user_pref(network.predictor.enabled, false); // 控制 Referer 发送策略同源发送跨域只发送 origin user_pref(network.http.sendRefererHeader, 2); user_pref(network.http.referer.XOriginTrimmingPolicy, 2); // 关闭 HTTP 缓存分区功能拆分为当前会话内使用 user_pref(privacy.partition.network_state, true); user_pref(privacy.partition.serviceworkers, true);这些参数的核心逻辑是减少浏览器主动告诉别人自己来了的行为。DNS 预取会在你尚未打开链接时就去请求那个域名的 DNS追踪方通过 DNS 日志就能建立你与目标站点的关联。把这个关掉是网络中继层面的基础卫生。4.2 Canvas 噪声注入与扩展层代码user.js 的privacy.resistFingerprinting本身就会对 Canvas 读取做噪声处理但它的实现是基于整数值扰动的效果比较粗暴。如果你希望做更精细的控制可以用 WebExtension 在内容脚本里包装 Canvas 读取方法// content-script.js const noisify (hex) { // 在色值末位做确定性扰动 const n parseInt(hex.slice(1), 16) ^ 0x1a2b3c4d; return # n.toString(16).padStart(8, 0); }; const wrapCanvas () { const originalToDataURL HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL function(...args) { const dataUrl originalToDataURL.apply(this, args); // 仅对超过阈值的 canvas 做扰动避免影响正常验证码等场景 if (this.width * this.height 10000) { return dataUrl.replace(/#[0-9a-f]{6}/gi, noisify); } return dataUrl; }; // toBlob、getImageData 同理不在文中展开 }; wrapCanvas();这个实现里我故意只对面积大于 10000 像素的画布做干扰因为小尺寸 Canvas 通常是图标、二维码、验证码扰动它们会影响正常业务。噪声值使用固定掩码异或保证同一页面内多次读取结果一致避免被站点脚本通过多次读取结果不同来反制。4.3 User-Agent 与环境参数的协同切换单一改 UA 没有意义camofox-browser 把与身份相关的参数全部收拢到一个 JSON 配置里{ identity: { ua: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:115.0) Gecko/20100101 Firefox/115.0, platform: Win32, oscpu: Windows NT 10.0; Win64; x64, language: zh-CN,zh;q0.8,en-US;q0.5, timezone: Asia/Shanghai, screen: { width: 1920, height: 1080, dpr: 1.0, colorDepth: 24 }, hardwareConcurrency: 8, deviceMemory: 8 } }扩展层读取这份配置后通过privacy.network和webRequestAPI 统一应用并同步修改navigator.platform、navigator.hardwareConcurrency等属性。这里最容易出错的是时区Firefox 的 RFP 会把时区固定为 UTC但如果你在 UA 里声称自己是别的国家用户时区却返回 UTC追踪系统一眼就能看出你在伪装。camofox 的选择是 RFP 开启时把所有语言和时区都统一到 UTC 加英文让伪装者的特征保持整体一致。4.4 字体列表的固定白名单方案字体枚举的拦截光靠 user.js 做不到必须在 WebExtension 里用 JS 层面的包装来实现。基本思路是把document.fonts.check()的行为覆盖掉并拦截基于宽度测量的字体探测// 字体白名单 const FONT_WHITELIST new Set([ Arial, Helvetica, Times New Roman, Courier New, Noto Sans, Noto Serif, DejaVu Sans, DejaVu Serif ]); const originalCheck FontFaceSet.prototype.check; FontFaceSet.prototype.check function(font, text) { const family font ? font.split( )[0].replace(/[]/g, ) : ; if (FONT_WHITELIST.has(family)) { return originalCheck.call(this, font, text); } return false; };不要小看这个看似简单的白名单。实际测试中很多反检测扩展只是让字体探测结果全为 false这会产生一个非常独特的没有任何字体存在的指纹特征反而更好识别。固定白名单的方案让探测结果与某一个真实的常规配置文件一致隐蔽性要好得多。5. 实测验证用反检测平台检验伪装效果配置写了一大堆效果怎么样得用数据说话。camofox-browser 的验证环节我分成了三块指纹哈希稳定性、反检测平台评分、以及完整会话下的跨维度一致性。5.1 测试环境与评估工具测试环境就是我的日常开发机Intel i7-12700、Windows 11、camofox-browser ESR 115 构建版。评估工具用了四个BrowserLeaks 套件browserleaks.com/canvas、/webgl、/webrtcAmIUniqueamiunique.orgFingerprintJS 官方 DemoCreepJS 综合检测页每个工具跑 10 次每次用全新 profile记录指纹哈希和各项指标。5.2 各项指纹指标的前后对比对比的基线是同一台机器上未做任何改动的 Firefox ESR 115 原版。下表记录了最具代表性的几个指标指标原版 Firefoxcamofox-browserCanvas 指纹哈希每次完全一致唯一性极高每次不同且同一会话内稳定WebGL 渲染器暴露真实 GPU 型号禁用/返回统一字符串UA 一致性与系统绑定与配置中心一致字体枚举数系统安装的 287 种固定白名单 8 种navigator 属性真实环境值统一预设值时区/语言真实时区 系统语言统一 UTC en-USFingerprintJS 识别唯一性高前后两次哈希相同低10 次内哈希变化 9 次BrowserLeaks 综合评分每项均暴露无一项暴露真实设备信息AmIUnique 给的原版 Firefox 结果是你的指纹在 190 万个访问者中是唯一的camofox 的结果是你的指纹与同配置的其他访问者有较强的共性。这个转变基本达成了立项目的从唯一的实体变成群体中的一个普通样本。5.3 持久化与稳定性验证伪装最大的敌人是不稳定。如果用户今天访问站 A 是 Windows 指纹明天变成 Linux 指纹站点脚本很容易判断这两个指纹属于同一个故意伪装的用户。所以持久化验证非常关键。我做了两类稳定性测试第一类是同一会话内的重复访问。连续访问同一指纹检测页 20 次确认 Canvas 噪声、UA、字体、媒体设备各项指标完全一致指纹哈希在会话内不漂移。测试结果同一会话内 20 次哈希一致符合要求。第二类是跨重启的长期一致性。使用固定 profile 重启浏览器 5 次每次检查各项指纹值。这里要特别提醒privacy.resistFingerprinting在 RFP 开启时会把navigator.pdfViewerEnabled和部分 WebGL 参数统一成固定值但某些扩展的随机化逻辑会在每次启动时重新生成 UA导致跨会话指纹漂移。我最终的做法是把随机身份写入 profile 目录下的camo-identity.json启动时先读这个文件存在就复用不存在才生成新的确保一个用户在一段时间内的身份是稳定的。6. 真实使用中的翻车现场与性能开销控制理论验证通过之后camofox-browser 进入了日常替代阶段随之而来的是各种翻车现场。这节的价值在我看来不低于前面的架构设计因为每一坑都是我花了好几天找出来的。6.1 翻车记录一全站强制 RFP 导致核心业务不可用打开 RFP 的第一周我遇到了三连击网银 U 盾登录页检测到环境异常直接拒绝视频平台进入全屏变成信箱模式画面四周一圈黑边某在线会议网页提示当前浏览器不支持音视频设备。前两个是 RFP 的预期行为第三个则是media.navigator.enabled被关掉的代价。排查之后我做了两处调整一是给网银首页加了域名例外RFP 在核心金融站点关闭二是准备了第二个日常折中档profile保留 WebRTC 但也启用了 mDNS 保护与 ICE 地址限制专门用来处理音视频通话需求。camofox 主配置保持激进日常要用的功能放到因站施策的策略上。这个教训很务实反指纹不是开关越猛越好你要优先明确自己的威胁模型。如果只是防广告追踪RFP 加固定 UA 完全够用如果要防精确设备识别才需要把 WebGL、字体这些全关掉而一旦关掉就要接受功能损失。6.2 翻车记录二多个扩展的优先级冲突第一版 camofox-browser 里我同时启用了三个扩展UA 切换、Canvas 噪声注入、字体白名单。本意是分工合作结果发现它们都尝试修改同一个 API在内容脚本的注入顺序上发生了冲突——UA 扩展先执行把navigator.platform改成了 Win32Canvas 扩展后执行又把页面里的脚本上下文重置了一遍导致部分站点报ReferenceError: navigator is not defined。排查链路很痛苦最后是通过逐个禁用扩展二分定位的。解决方案是收起三个扩展把功能合并成一个camo-extension在一个内容脚本里按顺序处理 UA、Canvas、字体、media 设备四件事用同一份配置驱动。合并后不仅冲突消失扩展之间的通信开销也省了。6.3 性能开销的量化与取舍反指纹不是免费的每一项伪装都有代价。我在 camofox-browser 上做了一组基础性能测试页面加载时间以新闻站和电商站各 5 个页面为准原版 1.8s 平均camofox 2.1s多出的 0.3s 主要来自脚本注入和 request 拦截。JavaScript 执行时间FingerprintJS 类脚本的执行时间从 35ms 涨到 420ms说明多余的噪声注入和保护逻辑确实让追踪脚本多跑了不少无用功这对用户来说反而是好事。内存占用常驻内存比原版多出约 180MB其中扩展层占了 120MB主要是热 JSON 配置和缓存数据。性能优化的路径我做了一个取舍对 DOM 操作较多的电商站采用延迟注入策略等页面 onload 后 3 秒再注入保护脚本这样首屏渲染基本不受影响。实测首屏速度损耗从 15% 降到 4% 左右而指纹保护效果没有任何衰减。还有一个经验不要把所有站点都塞进同一个 profile 里。我给 camofox-browser 设计了三个分档 profile——全保护档用于普通浏览和搜索平衡档保留 WebRTC 和全屏能力用于视频会议最低限档用于金融和政务类网站的强制兼容。用户根据当下场景手动切换比试图用一套配置应付所有情况要可靠得多。7. 扩展方向与最后想说的一句话camofox-browser 目前已经能作为我的日常主力浏览器稳定使用但距离我理想中的状态还有一段路。接下来打算做的两件事一是把身份参数的生成本地化做成每次安装时的独立初始化向导二是引入针对 TLS 指纹的本地代理层让浏览器和服务端之间的网络握手特征也纳入统一管理。前者已经在开发中后者涉及网络协议栈改造短时间可能落不了地。根据我的实际体会反指纹这件事最大的误区是追求绝对的隐形。真正的安全边界不是让追踪系统找不到你而是让它们从你身上找到的信息无法被信任、无法被关联、无法被用于决策。camofox-browser 的所有设计都围绕这三个目标展开也希望这篇项目记录能给正在做浏览器隐私改造的你省下几天的摸索时间。
返回列表