Saltar al contenido principal
fr/blog/obscura/research/web-fingerprinting/

obscura/research/web-fingerprinting

8 min de lecture

Aperçu

Signaux dérivés des fonctionnalités de la plateforme web, des capacités CSS, des polices et d'autres caractéristiques du navigateur.


1. Empreinte des Polices

Mécanisme

Les polices sont énumérées via plusieurs techniques :

  1. Mesure CSS @font-face : Créer du texte, mesurer sa largeur avec une police connue, puis essayer de charger une police système. Si la largeur change, la police existe.

  2. Canvas measureText : Approche similaire utilisant canvas.

  3. document.fonts.check() : API moderne pour vérifier la disponibilité des polices.


  4. Énumération basée sur Flash (legacy, encore utilisé) : Flash peut lister toutes les polices installées.


Exemple de Code

// Détection de police basée sur la mesure
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const text = 'mmmmmmmmmmlli'

function detectFont(font) {
  ctx.font = `72px ${font}, monospace`
  return ctx.measureText(text).width
}

// Comparer la largeur avec la référence monospace pour détecter

Entropie

La liste des polices varie considérablement :

Plateforme Polices Typiques Unicité
Windows 11 standard ~80 polices Moyenne
macOS Ventura+ ~120 polices Moyenne
Linux (varie selon distribution) 10-500 polices Très Élevée
Polices personnalisées installées Quelconque Très Élevée

Atténuation au Proxy

// Surcharger la mesure de police pour retourner des valeurs cohérentes
const originalMeasureText = CanvasRenderingContext2D.prototype.measureText
CanvasRenderingContext2D.prototype.measureText = function(text) {
  const result = originalMeasureText.call(this, text)
  // Arrondir la largeur pour réduire la précision de détection des polices
  result.width = Math.round(result.width / 10) * 10
  return result
}

Limitation : La détection des polices est seulement réduite, pas éliminée.


2. Support des Fonctionnalités du Navigateur (25+ vecteurs binaires)

Mécanisme

Chaque navigateur supporte un sous-ensemble différent d'API web et de fonctionnalités CSS. Collectivement, cela crée une empreinte navigateur+version.

Fonctionnalités à Haute Entropie

Fonctionnalité Chrome Firefox Safari
WebGL Oui Oui Oui
WebGPU Oui (flag) Non Non
WebUSB Oui Non Non
Web Bluetooth Oui Non Non
Web NFC Android Non Non
Web Authentication Oui Oui Oui
Web Assembly Oui Oui Oui
Service Workers Oui Oui Oui
Battery API Non Oui Non
Speech Synthesis Oui Oui Oui
GamePad API Oui Oui Oui (partiel)
WebRTC Oui Oui Oui
CSS Grid Oui Oui Oui
CSS backdrop-filter Oui Oui Oui
CSS Container Queries Oui Oui Oui

Entropie

Chaque fonctionnalité ajoute ~1 bit. Avec ~100 fonctionnalités testées, le maximum théorique est de 100 bits, mais les fonctionnalités sont hautement corrélées (entropie minimale au-delà de la famille et de la version du navigateur).

Entropie effective : ~8-12 bits (~256-4096 combinaisons).

Atténuation

Ne peut pas être fait au niveau proxy — ce sont des capacités inhérentes au navigateur. La détection de fonctionnalités s'exécute en JS avant que tout script injecté ne puisse s'exécuter (et même si surchargée, le navigateur signale toujours ses capacités réelles quelque part).


3. Empreinte par Requêtes Média CSS

Mécanisme

Les requêtes CSS @media peuvent forcer les dimensions d'écran, le DPI, la profondeur de couleur et d'autres propriétés sans JS :

@media (width: 1920px) { body::after { content: '1920' } }
@media (height: 1080px) { body::after { content: '1080' } }
@media (resolution: 1.5dppx) { body::after { content: '1.5' } }
@media (prefers-color-scheme: dark) { ... }
@media (prefers-reduced-motion: no-preference) { ... }
@media (hover: hover) { ... }
@media (pointer: fine) { ... }

Défi pour le Proxy

Le CSS s'exécute avant toute injection JS. Le proxy peut :

  1. Analyser le CSS dans le HTML de réponse et modifier les requêtes média
  2. Ou se fier au fait que les sondes d'empreinte CSS-only doivent exfiltrer des données (ce qui nécessite JS ou une requête réseau)

Véritable atténuation : Bloquer le chemin d'exfiltration (JS ou chargement d'image) plutôt que d'usurper les valeurs CSS.

Liste des Requêtes Média

Requête Valeurs Fuite
width px Largeur d'écran
height px Hauteur d'écran
aspect-ratio ratio Ratio d'écran
resolution dppx Densité de pixels
color bits Profondeur de couleur
color-gamut srgb/p3/rec2020 Espace colorimétrique
prefers-color-scheme light/dark Thème OS
prefers-reduced-motion no-preference/reduce Paramètre OS
prefers-contrast no-preference/more/less Paramètre OS
prefers-reduced-transparency no-preference/reduce Paramètre OS
hover none/hover Périphérique d'entrée
pointer none/coarse/fine Périphérique d'entrée
any-hover none/hover Tout périphérique
any-pointer none/coarse/fine Tout périphérique
display-mode fullscreen/standalone Mode PWA
prefers-color-gamut srgb/p3 Gamme d'affichage
dynamic-range standard/high Support HDR
inverted-colors none/inverted Accessibilité OS
monochrome bits Affichage monochrome
forced-colors none/active Accessibilité
prefers-reduced-data no-preference/reduce Économiseur de données

4. Empreinte ClientRects

Mécanisme

Element.getClientRects() retourne des valeurs légèrement différentes selon l'OS, le navigateur et le rendu des polices :

const span = document.createElement('span')
span.textContent = 'mmmmmmmmmmlli'
document.body.appendChild(span)
const rects = span.getClientRects()
// rects[0].width varie selon OS/navigateur/police

Atténuation

Element.prototype.getClientRects = function() {
  const rects = Element.prototype.__proto__.getClientRects.call(this)
  return Array.from(rects).map(r => ({
    ...r,
    width: Math.round(r.width / 10) * 10,
    height: Math.round(r.height / 10) * 10
  }))
}

5. Empreinte de l'API Web Audio

Voir audio-fingerprinting.md pour les détails.


6. Empreinte WebGPU

WebGPU fournit des informations structurées sur le périphérique, auparavant indisponibles :

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

// Les limites de l'adaptateur exposent des capacités GPU spécifiques
const limits = adapter.limits
// maxTextureDimension2D, maxStorageBufferBindingSize, etc.

// Ensemble de fonctionnalités
adapter.features.has('texture-compression-bc')
adapter.features.has('shader-f16')

Données Exposées

Champ Exemple Identifiabilité
vendor "nvidia", "apple", "amd", "intel" Faible (4-5 valeurs)
architecture "ampere", "rdna2", "tigerlake" Moyenne
device "integrated-graphics", "Apple M3" Moyenne
driver "460.32.03" Élevée
backend "Metal", "Vulkan", "D3D12" Faible
type "discrete-gpu", "integrated-gpu", "cpu" Faible
memoryHeaps taille et nombre Élevée
limits 50+ valeurs de capacité Très Élevée

Atténuation

// Bloquer les informations d'adaptateur WebGPU
navigator.gpu.requestAdapter = async function() {
  return null  // ou retourner un adaptateur minimal factice
}

Coût : Bloque WebGPU entièrement, ce qui casse certaines applications.


7. Persistance Service Worker

Mécanisme

Les Service Workers peuvent être utilisés pour identifier les utilisateurs entre sessions :

// Détecter si un SW est installé
navigator.serviceWorker.getRegistrations().then(regs => {
  if (regs.length > 0) console.log('SW trouvé')
})

Atténuation

navigator.serviceWorker.getRegistrations =
  async () => []

8. Empreinte des Extensions WebGL

WebGL expose une liste d'extensions supportées qui varie selon le GPU, le pilote et le navigateur :

Type d'Extension Nombre Exemple
WebGL standard ~30 OES_texture_float
WEBGL_* ~15 WEBGL_debug_renderer_info
EXT_* ~15 EXT_texture_filter_anisotropic
Spécifique au navigateur ~5 MOZ_WEBGL_*

Entropie : L'ensemble et le nombre exact d'extensions créent un hachage.

Atténuation :

const originalGetExtension = WebGLRenderingContext.prototype.getExtension
WebGLRenderingContext.prototype.getExtension = function(name) {
  if (name === 'WEBGL_debug_renderer_info') return null
  if (name.startsWith('MOZ_')) return null
  return originalGetExtension.call(this, name)
}

Conclusions pour Obscura

Vecteur Contrôle Proxy Stratégie d'Atténuation
Énumération des polices Partiel Arrondir les mesures, bloquer Flash
Support de fonctionnalités Aucun Accepter — inhérent au navigateur
Requêtes média CSS Partiel Bloquer l'exfiltration (DNS/réseau)
ClientRects Partiel Arrondir les valeurs via injection JS
WebGPU Partiel Bloquer les infos d'adaptateur
Service Workers Partiel Surcharger les API d'enregistrement
Extensions WebGL Partiel Filtrer la liste des extensions