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 :
-
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. -
Canvas
measureText: Approche similaire utilisant canvas. -
document.fonts.check(): API moderne pour vérifier la disponibilité des polices.
É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 :
- Analyser le CSS dans le HTML de réponse et modifier les requêtes média
- 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 |