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

obscura/research/web-fingerprinting

7 min de lectura

Resumen

Señales derivadas de características de la plataforma web, capacidades CSS, fuentes y otras características del navegador.


1. Fingerprinting de Fuentes

Mecanismo

Las fuentes se enumeran mediante varias técnicas:

  1. Medición CSS @font-face: Crear texto, medir su anchura con una fuente conocida, luego intentar cargar una fuente del sistema. Si la anchura cambia, la fuente existe.

  2. Canvas measureText: Enfoque similar usando canvas.

  3. document.fonts.check(): API moderna para verificar disponibilidad de fuentes.


  4. Enumeración basada en Flash (heredada, aún usada): Flash puede listar todas las fuentes instaladas.


Ejemplo de Código

// Detección de fuentes basada en medición
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
}

// Comparar anchura con la línea base monospace para detectar

Entropía

La lista de fuentes varía ampliamente:

Plataforma Fuentes Típicas Unicidad
Windows 11 stock ~80 fuentes Media
macOS Ventura+ ~120 fuentes Media
Linux (distro varía) 10-500 fuentes Muy Alta
Fuentes personalizadas instaladas Cualquiera Muy Alta

Mitigación en el Proxy

// Anular medición de fuentes para devolver valores consistentes
const originalMeasureText = CanvasRenderingContext2D.prototype.measureText
CanvasRenderingContext2D.prototype.measureText = function(text) {
  const result = originalMeasureText.call(this, text)
  // Redondear anchura para reducir precisión de detección de fuentes
  result.width = Math.round(result.width / 10) * 10
  return result
}

Limitación: La detección de fuentes solo se reduce, no se elimina.


2. Soporte de Características del Navegador (25+ vectores binarios)

Mecanismo

Cada navegador soporta un subconjunto diferente de APIs web y características CSS. Colectivamente, esto crea un fingerprint de navegador+versión.

Características de Alta Entropía

Característica Chrome Firefox Safari
WebGL
WebGPU Sí (flag) No No
WebUSB No No
Web Bluetooth No No
Web NFC Android No No
Web Authentication
Web Assembly
Service Workers
Battery API No No
Speech Synthesis
GamePad API Sí (parcial)
WebRTC
CSS Grid
CSS backdrop-filter
CSS Container Queries

Entropía

Cada característica añade ~1 bit. Con ~100 características probadas, el máximo teórico es 100 bits, pero las características están altamente correlacionadas (entropía mínima más allá de la familia del navegador y la versión).

Entropía efectiva: ~8-12 bits (~256-4096 combinaciones).

Mitigación

No se puede hacer a nivel de proxy — estas son capacidades inherentes del navegador. La detección de características se ejecuta en JS antes de que cualquier script inyectado pueda ejecutarse (e incluso si se anula, el navegador aún reporta las capacidades reales en algún lugar).


3. Fingerprinting mediante Media Queries CSS

Mecanismo

Las @media queries CSS pueden obtener por fuerza bruta dimensiones de pantalla, DPI, profundidad de color y otras propiedades sin 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) { ... }

Desafío para el Proxy

CSS se ejecuta antes de cualquier inyección JS. El proxy puede:

  1. Analizar el CSS en el HTML de respuesta y modificar las media queries
  2. O confiar en que las sondas de fingerprinting solo con CSS necesitan exfiltrar datos (lo que necesita JS o una petición de red)

Mitigación real: Bloquear la ruta de exfiltración (JS o carga de imágenes) en lugar de falsificar valores CSS.

Lista de Media Queries

Query Valores Información Filtrada
width px Ancho de pantalla
height px Alto de pantalla
aspect-ratio ratio Relación de pantalla
resolution dppx Densidad de píxeles
color bits Profundidad de color
color-gamut srgb/p3/rec2020 Espacio de color
prefers-color-scheme light/dark Tema del SO
prefers-reduced-motion no-preference/reduce Configuración del SO
prefers-contrast no-preference/more/less Configuración del SO
prefers-reduced-transparency no-preference/reduce Configuración del SO
hover none/hover Dispositivo de entrada
pointer none/coarse/fine Dispositivo de entrada
any-hover none/hover Cualquier entrada
any-pointer none/coarse/fine Cualquier entrada
display-mode fullscreen/standalone Modo PWA
prefers-color-gamut srgb/p3 Gama de pantalla
dynamic-range standard/high Soporte HDR
inverted-colors none/inverted Accesibilidad del SO
monochrome bits Pantalla monocromática
forced-colors none/active Accesibilidad
prefers-reduced-data no-preference/reduce Ahorro de datos

4. Fingerprinting de ClientRects

Mecanismo

Element.getClientRects() devuelve valores ligeramente diferentes según el SO, navegador y renderizado de fuentes:

const span = document.createElement('span')
span.textContent = 'mmmmmmmmmmlli'
document.body.appendChild(span)
const rects = span.getClientRects()
// rects[0].width varía según SO/navegador/fuente

Mitigación

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. Fingerprint de Web Audio API

Ver audio-fingerprinting.md para detalles.


6. Fingerprinting de WebGPU

WebGPU proporciona información estructurada del dispositivo no disponible anteriormente:

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

// Los límites del adapter exponen capacidades GPU específicas
const limits = adapter.limits
// maxTextureDimension2D, maxStorageBufferBindingSize, etc.

// Conjunto de características
adapter.features.has('texture-compression-bc')
adapter.features.has('shader-f16')

Datos Expuestos

Campo Ejemplo Identificabilidad
vendor "nvidia", "apple", "amd", "intel" Baja (4-5 valores)
architecture "ampere", "rdna2", "tigerlake" Media
device "integrated-graphics", "Apple M3" Media
driver "460.32.03" Alta
backend "Metal", "Vulkan", "D3D12" Baja
type "discrete-gpu", "integrated-gpu", "cpu" Baja
memoryHeaps tamaño y conteo Alta
limits 50+ valores de capacidad Muy Alta

Mitigación

// Bloquear información de WebGPU adapter
navigator.gpu.requestAdapter = async function() {
  return null  // o devolver un adapter mínimo falso
}

Costo: Bloquea WebGPU por completo, lo que rompe algunas aplicaciones.


7. Persistencia de Service Worker

Mecanismo

Los Service Workers pueden usarse para identificar usuarios entre sesiones:

// Detectar si un SW está instalado
navigator.serviceWorker.getRegistrations().then(regs => {
  if (regs.length > 0) console.log('SW encontrado')
})

Mitigación

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

8. Fingerprint de Extensiones WebGL

WebGL expone una lista de extensiones soportadas que varía según GPU, controlador y navegador:

Tipo de Extensión Conteo Ejemplo
WebGL estándar ~30 OES_texture_float
WEBGL_* ~15 WEBGL_debug_renderer_info
EXT_* ~15 EXT_texture_filter_anisotropic
Específicas del navegador ~5 MOZ_WEBGL_*

Entropía: El conjunto exacto y el conteo de extensiones crea un hash.

Mitigación:

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)
}

Conclusiones para Obscura

Vector Control del Proxy Estrategia de Mitigación
Enumeración de fuentes Parcial Redondear mediciones, bloquear Flash
Soporte de características Ninguno Aceptar — inherente al navegador
Media queries CSS Parcial Bloquear exfiltración (DNS/red)
ClientRects Parcial Redondear valores mediante inyección JS
WebGPU Parcial Bloquear información del adapter
Service Workers Parcial Anular APIs de registro
Extensiones WebGL Parcial Filtrar lista de extensiones