浏览器指纹
场景一:在网站上浏览某个商品,了解了相关的商品信息,但并没有下单购买,甚至没有进行登录操作。过两天用同台电脑访问其他网站的时候却发现很多同类商品的广告。
场景二:在某博客中你有多个小号(水军),这些小号的存在就是为了刷某个帖子的热度或者进行舆论引导,又或者纯粹进行流量交易,即便你在切换账号的时候清空了cookie、本地缓存,重开路由器甚至使用vpn来进行操作,你觉得自己足够小心,并尽可能提高水军的真实性,但是管理人员可能还是知道这是同一个人在操作,从而被打击。
什么是浏览器指纹
“浏览器指纹识别”是一种通过浏览器对网站可见的配置和设置信息来跟踪网络浏览器的方法,而不是传统的跟踪方法,例如 IP 地址和唯一的 cookie。
浏览器指纹识别既难以检测又极难阻止。就像我们人手上的指纹一样,具有个体辨识度,只不过现阶段浏览器指纹辨别的是浏览器。
当您加载网页时,您将自动将有关浏览器的某些信息广播到您正在访问的网站 - 以及嵌入该网站的任何跟踪器(例如那些提供广告的跟踪器)。您正在访问的站点可能会选择使用 JavaScript、Flash 和其他方法来分析您的浏览器(就像 Cover Your Tracks 一样)。它可能会查找您安装的字体类型、您设置的语言、您安装的附加组件以及其他因素。然后,该站点可能会创建一种您的个人资料,与与您的浏览器相关的这种特征模式相关联,而不是与特定的跟踪 cookie 相关联。
如果您的浏览器是独一无二的,那么即使没有设置跟踪 cookie,在线跟踪器也有可能识别出您的身份。虽然跟踪器不会知道您的姓名,但他们可以收集您访问的网站的深度个人档案。
删除 cookie 无济于事,因为正在分析的是浏览器配置的特征
背景
浏览器指纹追踪技术到目前已经进入2.5代。
- 第一代是状态化的,主要集中在用户的cookie和evercookie上,需要用户登录才可以得到有效的信息。
- 第二代才有了浏览器指纹的概念,通过不断增加浏览器的特征值从而让用户更具有区分度,例如(UA、浏览器插件信息)
- 第三代是已经将目光放在人身上了,通过收集用户的行为、习惯来为用户建立特征值甚至模型,可以实现真正的追踪技术,这部分目前实现比较复杂,依然在探索中。
目前处于2.5代是因为现在需要解决的问题是如何解决跨浏览器识别指纹的问题上
指纹采集
信息熵(entropy)是接收的每条消息中包含的信息的平均量,熵越高,则能传输越多的信息,熵越低,则意味着传输的信息越少
https://link.zhihu.com/?target=https%3A//panopticlick.eff.org/about
特征值:
- 每个浏览器的用户代理字符串
- 浏览器发送的HTTP ACCEPT标头
- 屏幕分辨率和色彩深度
- 系统设置为时区
- 浏览器中安装的浏览器扩展/插件,例如Quicktime,Flash,Java或Acrobat,以及这些插件的版本
- 计算机上安装的字体,由Flash或Java报告。
- 浏览器是否执行JavaScript脚本
- 浏览器是否能种下各种cookie和“超级cookie(super cookies)”
- 通过Canvas指纹生成的图像的哈希
- WebGL指纹生成的图像的哈希
- 是否浏览器设置为“Do Not Track”
- 系统平台(例如Win32,Linux x86)
- 系统语言(例如,cn,en-US)
- 浏览器是否支持触摸屏
浏览器指纹可以分为:
- 普通指纹:任何浏览器都具有的特征标识,比如硬件类型(Apple)、操作系统(Mac OS)、用户代理(User agent)、系统字体、语言、屏幕分辨率、浏览器插件 (Flash, Silverlight, Java, etc)、浏览器扩展、浏览器设置 (Do-Not-Track, etc)、时区差(Browser GMT Offset)等众多信息,这些指纹信息“类似”人类的身高、年龄等,有很大的冲突概率,只能作为辅助识别
- 高级指纹:
- Canvas指纹
- Audio Context指纹
- 硬件指纹
- 综合指纹
- WebGL 指纹
WebRTC
WebRTC(网页实时通信,Web Real Time Communication),是可以让浏览器有音视频实时通信的能力,它提供了三个主要的API来让JS可以实时获取和交换音视频数据,MediaStream、RTCPeerConnection和RTCDataChannel。当然如果要使用WebRTC获得通信能力,用户的真实ip就得暴露出来(NAT穿透),所以RTCPeerConnection就提供了这样的API,直接使用JS就可以拿到用户的IP地址。
Canvas指纹
Canvas是HTML5中的动态绘图标签,也可以用它生成图片或者处理图片。即便使用Canvas绘制相同的元素,但是由于系统的差别,字体渲染引擎不同,对抗锯齿、次像素渲染等算法也不同,canvas将同样的文字转成图片
function getCanvasFingerprint () {
var canvas = document.getElementById("anchor-uuid");
var context = canvas.getContext("2d");
context.font = "18pt Arial";
context.textBaseline = "top";
context.fillText("Hello, user.", 2, 2);
return canvas.toDataURL("image/jpeg");
}
AudioContext指纹
AudioContext指纹和Canvas类似也是基于硬件设备或者软件的差别,来产生不同的音频输出,然后计算得到不同的hash来作为标志,当然这里的音频并没有直接在浏览器中播放出来,只需要拿到播放前的处理数据就行
硬件指纹
硬件指纹主要通过检测硬件模块获取信息,作为对基于软件的指纹的补充,主要的硬件模块有:GPU’s clock frequency、Camera、Speakers/Microphone、Motion sensors、GPS、Battery等
综合指纹
Web世界的指纹碰撞不可避免,将上述所有的基本指纹和多种高级指纹综合利用,进行分析、计算哈希值作为综合指纹,可以大大降低碰撞率,极大提高客户端唯一性识别的准确性
webGL指纹
WebGL(Web图形库)是一个 JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,而无需使用插件。WebGL 通过引入一个与 OpenGL ES 2.0 非常一致的 API 来做到这一点,该 API 可以在 HTML5 元素中使用。这种一致性使 API 可以利用用户设备提供的硬件图形加速。网站可以利用 WebGL 来识别设备指纹,一般可以用两种方式来做到指纹生产:
- WebGL 报告——完整的 WebGL 浏览器报告表是可获取、可被检测的。在一些情况下,它会被转换成为哈希值以便更快地进行分析。
- WebGL 图像 ——渲染和转换为哈希值的隐藏 3D 图像。由于最终结果取决于进行计算的硬件设备,因此此方法会为设备及其驱动程序的不同组合生成唯一值。这种方式为不同的设备组合和驱动程序生成了唯一值。
可以通过 Browserleaks test 检测网站来查看网站可以通过该 API 获取哪些信息。
产生WebGL指纹原理是首先需要用着色器(shaders)绘制一个梯度对象,并将这个图片转换为Base64字符串。然后枚举WebGL所有的拓展和功能,并将他们添加到Base64字符串上,从而产生一个巨大的字符串,这个字符串在每台设备上可能是非常独特的。
如何防范
没有足够专业的知识或者非常频繁更换浏览器信息的话,几乎100%可以通过浏览器指纹定位到一个用户,当然这也不见得全是坏事。
- 泄露的隐私非常片面,只能说泄露了用户部分浏览网页时的行为。
- 价值不够,用户行为并未将实际的账户或者具体的人对应起来,产生的价值有限。
- 有益利用,利用浏览器指纹可以隔离部分黑产用户,防止刷票或者部分恶意行为。
Do Not Track
在http头部可以声明这样一个标志“DNT”意味“Do Not Track”,如果值为1表示为不要追踪我的网页行为,0则为可以追踪。即便没有cookie也可以通过这个标志符告诉服务器我不想被追踪到,不要记录我的行为。
不好的消息是大多数网站目前并没有遵守这个约定,完全忽略了“Do Not Track”这个信号。
EFF提供了这样一个工具Privacy Badger,它是一个浏览器插件形式的广告拦截器,对于那些遵守这个约定的公司会在这个广告拦截器的白名单上,允许显示广告,从而激励更多的公司遵守“Do Not Track”,以便完全展示广告。
个人觉得这一个方向很不错的做法,如果用户使用这个工具,网站在拿用户行为之前会抉择两边的利益,从而减轻用户对于隐私泄露的风险。
Tor Browser
浏览器的特征越多,越容易被追踪到,所以有效的方法是尽量将特征值进行大众化,例如目前市面最广泛的搭配是Window 10 + Chrome,那么你将UA改为这个组合就是一个有效的方法,同时尽量避免网站获取信息熵非常高的特征值,例如canvas指纹。
or 浏览器在这上面做了很多工作,以防止它们被用来跟踪Tor用户,为了响应Panopticlick和其他指纹识别实验,Tor浏览器现在包含一些补丁程序,以防止字体指纹(通过限制网站可以使用的字体)和Canvas指纹(通过检测对HTML5 Canvas对象的读取并要求用户批准)来防止,例如上面获取Canvas指纹的代码,在Tor上会弹出如下警告
同时还可以将Tor浏览器配置为主动阻止JavaScript。
禁用JS
比较粗暴的方法,直接禁止网站使用JavaScript可以非常有效地防御浏览器指纹追踪,但是这样会导致页面较大部分地功能不可用。但是,即便禁止了JS但是还可以通过CSS来采取浏览器的信息。如:
@media(device-width: 1080px) {
body {
background: url("https://example.org/1080.png");
}
}
隐身模式
隐私浏览和隐身模式只有一个目的。这些模式旨在防止您访问过的站点的痕迹被存储在您的机器上。这并不是为了阻止远程站点或跟踪器在您访问其服务器上的站点时识别和存储。
如果使用 Firefox,使用隐私浏览将提供一些针对跟踪器的保护。任何包含在断开连接跟踪保护列表中的跟踪器都将被阻止。这免受已知跟踪器的侵害。使用您的浏览器攻击您的已知指纹识别器和密码挖掘器也被阻止。但是,这不会阻止新的指纹识别器或跟踪器识别您的浏览器并密切关注它。为了获得这种额外的保护级别,您的浏览器需要有一个指纹,它是:
- 如此普遍以至于跟踪器无法将您与人群区分开来(如在Tor 浏览器中),或者
- 随机化,以便跟踪器无法从一个时刻到下一个时刻告诉您是您(如 勇敢的浏览器)。
Google 的 Chrome 浏览器在隐身模式下不提供针对跟踪器或指纹器的保护
如何防止被生成“用户指纹”
混淆Canvas指纹
想混淆 Canvas 指纹,只需要在 toDataURL 得到的结果上做手脚就可以。
toDataURL() 将整个canvas的内容导出,我们需要将 Canvas 中的部分内容修改,这个时候可以通过 getImageData() 复制画布上指定矩形的像素数据,然后通过 **putImageData()**将图像数据放回,然后再使用 toDataURL() 导出的图片就有了差异。
CanvasRenderingContext2D.getImageData() 返回一个ImageData对象,用来描述 Canvas 区域隐含的像素数据。这个区域通过矩形表示,起始点为(sx, sy)、宽为sw、高为sh。
ImageData 接口描述了元素的一个隐含像素数据的区域,可以由 ImageData() 方法构造,或者由canvas 在一起的 CanvasRenderingContext2D 对象的创建方法:createImageData() 和 getImageData()。
ImageData 对象存储着canvas对象真实的像素数据,它包含几个只读属性:
- width 图片宽度,单位像素
- height 图片高度,单位像素
- data
Uint8ClampedArray 类型的一位数组,包含着 RGBA 的整型数据,范围在 0~255。它可以视作初始像素数据,每个像素用 4 个 1 bytes 值(按照 red、green、blue、alpha 的顺序),每个颜色值用0~255 中的数字代表。每个部分被分配到一个数组内的连续索引,左上角第一个像素的红色部分,位于数组索引的第 0 位。像素从左到右从上到下被处理,遍历整个数组。
Unit8ClampedArray 包含 高度宽度4 bytes数据,索引值从 0 ~ (wh4)-1 。
读取图片中位于第 50 行,200 列的像素的蓝色部分
const blueComponent = imageData[50*(imageData.width * 4) + 200*4 + 2]
实现混淆Canvas指纹的方法
const toBlob = HTMLCanvasElement.prototype.toBlob;
const toDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.manipulate = function() {
const {width, height} = this;
// 拿到在进行toDataURL或者toBlob前的canvas所生成的CanvasRenderingContext2D
const context = this.getContext('2d');
const shift = {
'r': Math.floor(Math.random() * 10) - 5,
'g': Math.floor(Math.random() * 10) - 5,
'b': Math.floor(Math.random() * 10) - 5
};
const matt = context.getImageData(0, 0, width, height);
// 对getImageData生成的imageData(像素源数据)中的每一个像素的r、g、b部分的值进行进行随机改变从而生成唯一的图像。
for (let i = 0; i < height; i += Math.max(1, parseInt(height / 10))) {
for (let j = 0; j < width; j += Math.max(1, parseInt(width / 10))) {
const n = ((i * (width * 4)) + (j * 4));
matt.data[n + 0] = matt.data[n + 0] + shift.r; // 加上随机扰动
matt.data[n + 1] = matt.data[n + 1] + shift.g;
matt.data[n + 2] = matt.data[n + 2] + shift.b;
}
}
context.putImageData(matt, 0, 0); // 重新放回去
// 修改prototype.toBlob
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function() {
if (script.dataset.active === 'true') {
try {
this.manipulate(); // 在每次toBlob前,先混淆下ImageData
}
catch(e) {
console.warn('manipulation failed', e);
}
}
return toBlob.apply(this, arguments);
}
});
// 修改prototype. toDataURL
Object.defineProperty(HTMLCanvasElement.prototype, 'toDataURL', {
value: function() {
if (script.dataset.active === 'true') {
try {
this.manipulate(); // 在每次toDataURL前,先混淆下ImageData
}
catch(e) {
console.warn('manipulation failed', e);
}
}
return toDataURL.apply(this, arguments);
}
});
混淆其他指纹
与混淆canvas指纹思路一致,都是更改被获取对象的原型的方法。
比如混淆时区,就是更改 Date.prototype.getTimezoneOffset 的返回值。
混淆分辨率则是更改documentElement.clientHeight documentElement.clientWidth
混淆 WebGL 则要更改 WebGLbufferData getParameter方法等等