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

obscura/research/canvas-webgl-fingerprinting

3 Min. Lesezeit

Übersicht

Canvas-Fingerprinting nutzt subtile Unterschiede in der Art und Weise aus, wie Hardware und Software Grafiken rendern. Diese Unterschiede entstehen durch:

  • GPU-Modell und Treiberversion
  • Betriebssystem-Schriftdarstellung (Anti-Aliasing, Hinting, Subpixel-Rendering)
  • Browser-Rendering-Engine
  • Installierte Systemschriften

Canvas-Fingerprinting

Mechanismus

  1. Eine Website zeichnet Formen und Text auf ein HTML5-<canvas>-Element
  2. Ruft canvas.toDataURL() auf, um Pixeldaten als base64 zu extrahieren
  3. Hasht das Ergebnis (MD5, SHA-1 oder benutzerdefinierter Hash)
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) // einzigartig pro Gerät

Entropiequellen

Quelle Variabilität
Schriftdarstellung (Anti-Aliasing, Hinting) Hoch
Subpixel-Rendering-Reihenfolge (RGB vs. BGR) Mittel
GPU-Rasterisierung von Formen Mittel
Farbraumkonvertierung Niedrig

WebGL-Fingerprinting

Mechanismus

WebGL gibt die GPU direkt über Folgendes preis:

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

Zusätzlich erzeugt das Rendern von 3D-Szenen hardwarespezifische Pixelausgaben.

Signale

  • UNMASKED_RENDERER_WEBGL – genaues GPU-Modell
  • UNMASKED_VENDOR_WEBGL – GPU-Hersteller
  • WebGL-Shader-Präzision
  • MAX_TEXTURE_SIZE, MAX_VERTEX_ATTRIBS, etc.
  • Rendering einer 3D-Szene (Hash der Pixel)

WebGPU-Fingerprinting

WebGPU (der Nachfolger von WebGL) gibt noch detailliertere Informationen preis:

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

Signale

  • adapterInfo.vendor
  • adapterInfo.architecture
  • adapterInfo.device
  • Feature-Set (welche GPU-Features unterstützt werden)

Warum Spoofing auf Proxy-Ebene schwierig ist

Canvas-/WebGL-/WebGPU-Rendering findet auf der lokalen GPU statt. Der Proxy sieht nur den Netzwerkverkehr. Bis die Daten den Proxy erreichen, ist das Rendering bereits abgeschlossen.

Mögliche Abschwächungsansätze:


  1. API blockieren: WebGL null zurückgeben lassen, Canvas einen Fehler werfen lassen. Problem: erkennbar als Signal ("WebGL blockiert" ist selbst ein Fingerprint-Merkmal).


  2. JS-Rauschen injizieren: CanvasBlocker (Firefox) randomisiert die Canvas-Ausgabe, indem vor toDataURL() ein zufälliges Pixel injiziert wird. Problem: Wenn die Website Canvas 3-mal liest und 3 verschiedene Hashes erhält, erkennt sie Manipulation. Erfordert zudem frequenzkonsistentes Rauschen.

  3. Image-Data-Override via Proxy: CanvasRenderingContext2D.prototype.getImageData per injiziertem JS überschreiben, um generische Daten zurückzugeben. Problem: funktioniert nur mit getImageData, nicht mit toDataURL. Mehrere Wege, um Pixel zu extrahieren.


  4. Software-GPU-Rendering (SwiftShader/Lavapipe): GPU-Rendering durch CPU-basierten deterministischen Renderer ersetzen. Problem: massive Leistungseinbußen, keine 3D-Beschleunigung.


Formeln

Canvas-Fingerprint-Entropie

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

Wobei P(xi)P(x_i) die Wahrscheinlichkeit eines gegebenen Hashes über verschiedene Geräte hinweg ist.

Geschätzte Entropie: ~5-7 Bits (kann ~32-128 Geräte unterscheiden).

Einzigartigkeits-Wahrscheinlichkeit

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

Wobei NN die Bevölkerungsgröße und kk die Anzahl der unterschiedlichen Canvas-Ausgaben in der Stichprobe ist.

Schlussfolgerungen

  • Canvas-/WebGL-Fingerprinting kann auf Proxy-Ebene nicht vollständig gespooft werden
  • Rausch-Injektion per JS ist eine teilweise Abschwächung, aber erkennbar
  • Das Blockieren dieser APIs ist erkennbar und wird selbst zu einem Signal
  • Die einzige nahezu vollständige Lösung ist der Ansatz des Tor-Browsers (alle Benutzer auf identische Ausgabe normalisieren)
  • Für Obscura: Bester Ansatz ist injiziertes JS, das konsistentes Rauschen hinzufügt + WEBGL_debug_renderer_info blockiert