客户端追踪 — 浏览器设备指纹

嗯,想来也是来公司做验证码接触安全也快半年了,来讲下做安全沉淀下来的一些小的积累吧。

Canvas指纹

Canvas指纹也是现在最常用的设备指纹收集方式之一,其原理是:

因为相同的HTML5 Canvas元素绘制操作,在不同操作系统、不同浏览器上,产生的图片内容不完全相同。在图片格式上,不同浏览器使用了不同的图形处理引擎、不同的图片导出选项、不同的默认压缩级别等。在像素级别来看,操作系统各自使用了不同的设置和算法来进行抗锯齿和子像素渲染操作。即使相同的绘图操作,产生的图片数据的CRC检验也不相同。

  let canvas = document.createElement('canvas');
  let ctx = canvas.getContext("2d");
  ctx.font = "24px Arial";
  ctx.fillText("Hello Panda",22,33);
  ctx.moveTo(0,60);
  ctx.lineTo(100,60);
  ctx.stroke();
  let b64 = canvas.toDataURL().replace("data:image/png;base64,","");

其中主要就是在画布上画出相同的图案之后使用 toDataURL() 这个API来获取生成图片内容的base64编码。由于获取的base64字符串一般较长,所以截取其中一部分作为指纹来使用也是大多数开发者选择的做法。

AudioContext指纹

单一的使用单个指纹来作为客户端判别还是有几率出现碰撞的,一般设备指纹的收集都是通过多重维度来进行的。

获取显卡指纹的原理一般大致如下:

方法一:生成音频信息流(三角波),对其进行FFT变换,计算SHA值作为指纹,音频输出到音频设备之前进行清除,用户毫无察觉。

方法二:生成音频信息流(正弦波),进行动态压缩处理,计算MD5值。

  window.AudioContext = window.AudioContext || window.webkitAudioContext;
  let audioCtx = new AudioContext();
  let oscillator = audioCtx.createOscillator();
  let gainNode = audioCtx.createGain();
  oscillator.connect(gainNode);
  gainNode.connect(audioCtx.destination);
  oscillator.type = 'sine';
  oscillator.frequency.value = 196.00;
  gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
  gainNode.gain.linearRampToValueAtTime(1, audioCtx.currentTime + 0.01);
  oscillator.start(audioCtx.currentTime);
  gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1);
  oscillator.stop(audioCtx.currentTime + 1);

AudioContext 是收集客户端的声卡指纹,其在 相同型号的设备 / 相同的浏览器 上均可能会产生不同的指纹。主要是主机或浏览器硬件或软件的细微差别,导致音频信号的处理上的差异,相同器上的同款浏览器产生相同的音频输出,不同机器或不同浏览器产生的音频输出会存在差异。

一般声卡指纹和显卡指纹一起计算哈希之后的碰撞概率基本忽略不计。

webGL指纹

其他方案

在介绍了三种常用的web设备指纹收集之后,其实还有很多的设备信息可以用来持续的追踪用户。

比如使用 webRTC 来获取用户的ip, 通过 webGL API 中的 WEBGL_debug_renderer_info 来获取显卡信息。

  let canvas = document.createElement("canvas");
  let gl = canvas.getContext("experimental-webgl");
  let debugInfo = gl.getExtension("WEBGL_debug_renderer_info");

  gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);   // 显卡供应商
  gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL); // 显卡渲染器

还可以通过 Battery Status来获取电池使用的信息。

  navigator.getBattery().then(function(battery) {
    battery.charging;               // 是否在充电
    battery.level;                  // 电量比例
    battery.chargingTime;           // 充电时长
    battery.dischargingTime;        // 放电时长
    /* 事件 */
    battery.onchargingchange        // 充电状态放生变化
    battery.onlevelchange           // 电量发生变化
    battery.onchargingtimechange    // 充电时长发生变化
    battery.ondischargingtimechange // 放电时长发生变化
  });

发表评论

电子邮件地址不会被公开。 必填项已用*标注