Detección automática de formatos de imagen - Identificación de archivos mediante números mágicos
Por qué las extensiones de archivo no son suficientes para detectar el formato
Las extensiones de archivo pueden modificarse arbitrariamente y no son una base confiable para determinar el formato real de un archivo. El manejo seguro de archivos debe inspeccionar el contenido del archivo en sí.
Por qué las extensiones no son confiables:
- El usuario puede renombrar libremente: cambiar .exe a .jpg puede eludir verificaciones simples basadas en extensión
- Ataques de carga: usuarios malintencionados cargan archivos de script disfrazados como imágenes; si el servidor solo verifica la extensión, será engañado
- Desajuste de formato: un usuario puede guardar un archivo PNG con extensión .jpg, causando errores en la lógica de procesamiento
- Sin extensión: algunos archivos generados por sistemas pueden no tener extensión
Enfoque correcto: leer los primeros bytes del archivo (número mágico/firma de archivo) y compararlos con las firmas de formatos conocidos. Esta es la verdadera identificación del formato de archivo y no puede falsificarse mediante renombrado.
Cómo funcionan los números mágicos - Referencia de firmas de los principales formatos de imagen
Un número mágico (Magic Number) es una secuencia específica de bytes en una posición fija al inicio del archivo, utilizada para identificar el formato. Cada formato de imagen tiene una firma única.
Firmas de los principales formatos de imagen:
- JPEG:
FF D8 FF(primeros 3 bytes). Marcador de fin:FF D9 - PNG:
89 50 4E 47 0D 0A 1A 0A(8 bytes, contiene "PNG" en ASCII) - GIF:
47 49 46 38("GIF8", seguido de "7a" o "9a" indicando la versión) - WebP:
52 49 46 46 ?? ?? ?? ?? 57 45 42 50(contenedor RIFF + "WEBP") - AVIF:
?? ?? ?? ?? 66 74 79 70 61 76 69 66("ftypavif" en el desplazamiento de 4 bytes) - BMP:
42 4D("BM") - TIFF:
49 49 2A 00(little-endian) o4D 4D 00 2A(big-endian) - SVG: formato de texto, verificar si comienza con
<svgo<?xml
Bytes mínimos necesarios para la detección: la mayoría de los formatos se identifican con precisión con solo los primeros 12 bytes. Leer los primeros 16 bytes cubre todos los formatos de imagen comunes.
Detección de formato en JavaScript - Implementación para navegador y Node.js
Implementación de detección de formato de imagen en navegador y Node.js para validación de cargas y procesamiento de archivos.
Implementación en navegador:
- Usar
FileReader.readAsArrayBuffer()para leer los primeros N bytes del archivo - Crear una vista
Uint8Arraypara inspeccionar los valores de bytes - Ejemplo:
const bytes = new Uint8Array(buffer.slice(0, 16)) - Verificación:
if (bytes[0] === 0xFF && bytes[1] === 0xD8) return "jpeg"
Implementación en Node.js:
- Usar
fs.read()para leer solo los primeros 16 bytes, sin necesidad de cargar el archivo completo - O usar la librería
file-type:const {fileTypeFromBuffer} = require("file-type") - Detección en streaming:
fileTypeFromStream(readableStream)adecuado para archivos grandes
Detección rápida de Blob/File:
file.slice(0, 16)crea un Blob con solo los primeros 16 bytes- Combinado con
FileReaderpara lectura asíncrona sin bloquear el hilo principal - Validar inmediatamente en el evento change de la selección de archivo (
input[type=file])
Validación segura de formato en el servidor
La validación de formato en el servidor es la última línea de defensa de seguridad. Incluso si el frontend ya validó, el servidor debe validar independientemente, ya que la validación del frontend puede ser eludida.
Estrategia de validación:
- Verificación de número mágico: leer los primeros 16 bytes del archivo cargado y verificar que la firma coincida con el formato declarado
- Verificación de integridad: intentar decodificar la imagen con una librería (como Sharp, Pillow). Si se decodifica exitosamente, es una imagen válida
- Límite de dimensiones: verificar las dimensiones en píxeles después de la decodificación para prevenir bombas de descompresión (como 1x1 píxel pero declarando 100000x100000)
- Recodificación: recodificar la imagen cargada al formato objetivo, eliminando posibles datos maliciosos incrustados
Defensa contra ataques comunes:
- Archivos polimórficos: archivos que son simultáneamente una imagen válida y un script válido. Se eliminan mediante recodificación
- XSS en SVG: los SVG pueden contener JavaScript. Se debe sanitizar el contenido SVG o convertir a formato rasterizado
- Bombas de descompresión: imágenes con ratio de compresión extremadamente alto que consumen gran cantidad de memoria al descomprimir. Establecer un límite máximo de píxeles
Sniffing de tipo MIME y comportamiento del navegador
Los navegadores realizan sniffing de tipo MIME al procesar recursos: incluso si el servidor declara un Content-Type, el navegador puede determinar el tipo por sí mismo según el contenido.
Mecanismo de sniffing MIME:
- El navegador inspecciona los primeros bytes del cuerpo de la respuesta y los compara con firmas conocidas
- Si el tipo detectado no coincide con el Content-Type, el navegador puede usar el tipo detectado
- Esto es un riesgo de seguridad: los atacantes pueden explotar el sniffing para que el navegador ejecute una imagen como HTML/JS
Configuración de encabezados de seguridad:
X-Content-Type-Options: nosniff: prohíbe al navegador realizar sniffing MIME, usando estrictamente el tipo declarado por el servidor- Todas las respuestas de imágenes deben incluir este encabezado para prevenir ataques de confusión de tipo de contenido
Configuración correcta de Content-Type:
- Establecer el Content-Type según el contenido real del archivo (resultado de detección por número mágico), no según la extensión
- Mapeos comunes: JPEG →
image/jpeg, PNG →image/png, WebP →image/webp, AVIF →image/avif, SVG →image/svg+xml
Detección avanzada - Formatos contenedor e identificación multicapa
Algunos formatos de imagen modernos utilizan estructuras de contenedor (como ISOBMFF, RIFF), requiriendo analizar el interior del contenedor para determinar el formato específico.
Contenedor ISOBMFF (HEIF/AVIF):
- HEIF, AVIF y HEIC utilizan el contenedor ISO Base Media File Format
- El box ftyp en el desplazamiento de 4 bytes identifica el formato específico:
avif,heic,mif1 - Es necesario analizar la estructura de boxes para distinguir entre AVIF y HEIC
Contenedor RIFF (WebP):
- WebP usa el contenedor RIFF, los primeros 4 bytes son "RIFF", en el desplazamiento 8 está "WEBP"
- Un análisis más profundo puede determinar si es con pérdida (VP8), sin pérdida (VP8L) o animado (ANIM)
Formatos derivados de TIFF:
- DNG, CR2 (Canon RAW), NEF (Nikon RAW) están basados en la estructura TIFF
- Es necesario analizar etiquetas específicas en el IFD de TIFF para distinguirlos
Recomendaciones de implementación:
- Para aplicaciones web, generalmente solo es necesario identificar seis formatos: JPEG/PNG/GIF/WebP/AVIF/SVG
- Usar librerías maduras (file-type, python-magic) en lugar de implementar el análisis completo por cuenta propia
- Para formatos desconocidos, devolver null/undefined en lugar de adivinar, dejando que el llamador decida cómo proceder