Saltar al contenido principal
es/blog/obscura/research/canvas-webgl-fingerprinting/

obscura/research/canvas-webgl-fingerprinting

4 min de lectura

Resumen

El fingerprinting de Canvas explota diferencias sutiles en cómo el hardware y el software renderizan gráficos. Estas diferencias surgen de:

  • Modelo de GPU y versión del controlador
  • Renderizado de fuentes del sistema operativo (anti-aliasing, hinting, renderizado subpíxel)
  • Motor de renderizado del navegador
  • Fuentes del sistema instaladas

Fingerprinting de Canvas

Mecanismo

  1. El sitio dibuja formas y texto en un elemento <canvas> de HTML5
  2. Llama a canvas.toDataURL() para extraer datos de píxeles como base64
  3. Hashea el resultado (MD5, SHA-1, o hash personalizado)
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
ctx.font = '14px Arial'
ctx.fillStyle = '#f60'
ctx.fillRect(125, 1, 62, 20)
ctx.fillStyle = '#069'
ctx.fillText('BrowserLeaks,com <canvas> 1.0', 2, 15)

const fingerprint = canvas.toDataURL()
const hash = md5(fingerprint) // único por dispositivo

Fuentes de Entropía

Fuente Variabilidad
Renderizado de fuentes (anti-aliasing, hinting) Alta
Orden de renderizado subpíxel (RGB vs BGR) Media
Rasterización GPU de formas Media
Conversión de espacio de color Baja

Fingerprinting de WebGL

Mecanismo

WebGL expone la GPU directamente mediante:

const gl = canvas.getContext('webgl')
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info')
const renderer = debugInfo.UNMASKED_RENDERER_WEBGL
// ej., "NVIDIA GeForce RTX 3080/PCIe/SSE2"
const vendor = debugInfo.UNMASKED_VENDOR_WEBGL
// ej., "NVIDIA Corporation"

Además, el renderizado de escenas 3D produce salida de píxeles específica del hardware.

Señales

  • UNMASKED_RENDERER_WEBGL — modelo exacto de GPU
  • UNMASKED_VENDOR_WEBGL — fabricante de GPU
  • Precisión de shader WebGL
  • MAX_TEXTURE_SIZE, MAX_VERTEX_ATTRIBS, etc.
  • Renderizado de escena 3D (hash de píxeles)

Fingerprinting de WebGPU

WebGPU (el sucesor de WebGL) expone información aún más granular:

const adapter = await navigator.gpu.requestAdapter()
const info = await adapter.requestAdapterInfo()
// { vendor: "nvidia", architecture: "ampere", device: "..." }

Señales

  • adapterInfo.vendor
  • adapterInfo.architecture
  • adapterInfo.device
  • Conjunto de características (qué funcionalidades GPU están soportadas)

Por Qué es Difícil la Falsificación a Nivel de Proxy

El renderizado de Canvas/WebGL/WebGPU ocurre en la GPU local. El proxy solo ve el tráfico de red. Cuando los datos llegan al proxy, el renderizado ya está completo.

Enfoques de mitigación posibles:


  1. Bloquear la API: Hacer que WebGL devuelva null, que canvas lance un error. Problema: detectable como señal ("WebGL bloqueado" es en sí mismo un rasgo de fingerprint).


  2. Inyectar ruido JS: CanvasBlocker (Firefox) aleatoriza la salida del canvas inyectando un píxel aleatorio antes de toDataURL(). Problema: si el sitio lee el canvas 3 veces y obtiene 3 hashes diferentes, detecta la manipulación. También requiere ruido consistente en frecuencia.

  3. Anulación de datos de imagen mediante Proxy: Anular CanvasRenderingContext2D.prototype.getImageData mediante JS inyectado para devolver datos genéricos. Problema: solo funciona en getImageData, no en toDataURL. Múltiples rutas para extraer píxeles.


  4. Renderizado GPU por software (SwiftShader/Lavapipe): Reemplazar el renderizado GPU con un renderizador determinista basado en CPU. Problema: costo masivo de rendimiento, sin aceleración 3D.


Fórmulas

Entropía del Fingerprint de Canvas

Hcanvas=i=1nP(xi)log2P(xi)H_{canvas} = -\sum_{i=1}^{n} P(x_i) \log_2 P(x_i)

Donde P(xi)P(x_i) es la probabilidad de un hash dado en diferentes dispositivos.

Entropía estimada: ~5-7 bits (puede distinguir ~32-128 dispositivos).

Probabilidad de Unicidad

P(unique)=1i=1kNiNP(\text{unique}) = 1 - \prod_{i=1}^{k} \frac{N - i}{N}

Donde NN es el tamaño de la población y kk es el número de salidas de canvas distintas en la muestra.

Conclusiones

  • El fingerprinting de Canvas/WebGL no puede falsificarse completamente a nivel de proxy
  • La inyección de ruido mediante JS es una mitigación parcial pero detectable
  • Bloquear estas APIs es detectable y se convierte en una señal en sí misma
  • La única solución casi completa es el enfoque de Tor Browser (normalizar a todos los usuarios a una salida idéntica)
  • Para Obscura: el mejor enfoque es JS inyectado que añade ruido consistente + bloquea WEBGL_debug_renderer_info