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

obscura/research/canvas-webgl-fingerprinting

3 min di lettura

Panoramica

Il fingerprinting Canvas sfrutta le sottili differenze nel modo in cui hardware e software renderizzano la grafica. Queste differenze derivano da:

  • Modello GPU e versione del driver
  • Rendering dei font del sistema operativo (anti-aliasing, hinting, rendering subpixel)
  • Motore di rendering del browser
  • Font di sistema installati

Canvas Fingerprinting

Meccanismo

  1. Il sito disegna forme e testo su un elemento HTML5 <canvas>
  2. Chiama canvas.toDataURL() per estrarre i dati dei pixel come base64
  3. Applica un hash al risultato (MD5, SHA-1, o hash personalizzato)
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) // unico per dispositivo

Fonti di Entropia

Fonte Variabilità
Rendering font (anti-aliasing, hinting) Alta
Ordine rendering subpixel (RGB vs BGR) Media
Rasterizzazione GPU delle forme Media
Conversione spazio colore Bassa

WebGL Fingerprinting

Meccanismo

WebGL espone la GPU direttamente tramite:

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

Inoltre, il rendering di scene 3D produce output pixel specifici dell'hardware.

Segnali

  • UNMASKED_RENDERER_WEBGL: modello esatto della GPU
  • UNMASKED_VENDOR_WEBGL: produttore GPU
  • Precisione shader WebGL
  • MAX_TEXTURE_SIZE, MAX_VERTEX_ATTRIBS, ecc.
  • Rendering scena 3D (hash dei pixel)

WebGPU Fingerprinting

WebGPU (il successore di WebGL) espone informazioni ancora più granulari:

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

Segnali

  • adapterInfo.vendor
  • adapterInfo.architecture
  • adapterInfo.device
  • Set di funzionalità (quali funzionalità GPU sono supportate)

Perché lo Spoofing a Livello di Proxy è Difficile

Il rendering Canvas/WebGL/WebGPU avviene sulla GPU locale. Il proxy vede solo il traffico di rete. Quando i dati raggiungono il proxy, il rendering è già completo.

Approcci di mitigazione possibili:


  1. Bloccare l'API: Far sì che WebGL restituisca null, canvas generi un errore. Problema: rilevabile come segnale ("WebGL bloccato" è di per sé un tratto di fingerprint).


  2. Iniettare rumore JS: CanvasBlocker (Firefox) randomizza l'output canvas iniettando un pixel casuale prima di toDataURL(). Problema: se il sito legge canvas 3 volte e ottiene 3 hash diversi, rileva la manomissione. Richiede anche rumore a frequenza costante.

  3. Override ImageData tramite Proxy: Sovrascrivere CanvasRenderingContext2D.prototype.getImageData tramite JS iniettato per restituire dati generici. Problema: funziona solo su getImageData, non su toDataURL. Percorsi multipli per estrarre pixel.


  4. Rendering GPU Software (SwiftShader/Lavapipe): Sostituire il rendering GPU con un renderer deterministico basato su CPU. Problema: costo prestazionale massiccio, nessuna accelerazione 3D.


Formule

Entropia del Fingerprint Canvas

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

Dove P(xi)P(x_i) è la probabilità di un dato hash su diversi dispositivi.

Entropia stimata: ~5-7 bit (può distinguere ~32-128 dispositivi).

Probabilità di Unicità

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

Dove NN è la dimensione della popolazione e kk è il numero di output canvas distinti nel campione.

Conclusioni

  • Il fingerprinting Canvas/WebGL non può essere completamente falsificato a livello di proxy
  • L'iniezione di rumore tramite JS è una mitigazione parziale ma rilevabile
  • Bloccare queste API è rilevabile e diventa esso stesso un segnale
  • L'unica soluzione quasi completa è l'approccio di Tor Browser (normalizzare tutti gli utenti con output identico)
  • Per Obscura: l'approccio migliore è JS iniettato che aggiunge rumore coerente + blocca WEBGL_debug_renderer_info