Vulnerabilidades de seguridad en archivos de imagen - Validación de carga y defensa del lado del servidor
Panorama de riesgos de seguridad en la carga de imágenes
La funcionalidad de carga de imágenes es una de las características más comunes en aplicaciones web y, al mismo tiempo, uno de los puntos de entrada más atacados. Los atacantes suben archivos maliciosos disfrazados de imágenes para lograr ejecución de código en el servidor, XSS (Cross-Site Scripting) o DoS (Denegación de Servicio).
Principales tipos de ataque:
- Archivos disfrazados: Subir scripts maliciosos (PHP/JSP) disfrazados como archivos de imagen
- Archivos políglotas (Polyglot): Archivos que son simultáneamente imágenes válidas y scripts válidos
- Vulnerabilidades en bibliotecas de procesamiento: Explotar fallos en ImageMagick y otras bibliotecas para ejecutar comandos
- SVG XSS: Incrustar JavaScript en SVG para ataques de scripting entre sitios
- Inyección EXIF: Inyectar código malicioso en metadatos
- Agotamiento de recursos: Subir imágenes comprimidas que al descomprimirse agotan la memoria del servidor (bombas de descompresión)
Principio de defensa: Nunca confiar en ninguna información proporcionada por el cliente (nombre de archivo, Content-Type, extensión). Realizar validación y procesamiento multicapa en el servidor.
Validación de bytes mágicos - Determinar el formato real del archivo
El formato real de un archivo se determina por sus bytes mágicos (Magic Bytes) en la cabecera, no por la extensión ni el tipo MIME.
Bytes mágicos de formatos de imagen comunes:
- JPEG:
FF D8 FF(primeros 3 bytes) - PNG:
89 50 4E 47 0D 0A 1A 0A(primeros 8 bytes) - GIF:
47 49 46 38("GIF8") - WebP:
52 49 46 46 ... 57 45 42 50(RIFF...WEBP)
Implementación de validación:
const MAGIC = { jpeg: [0xFF, 0xD8, 0xFF], png: [0x89, 0x50, 0x4E, 0x47],};function validateMagic(buffer, expected) { return expected.every((byte, i) => buffer[i] === byte);}
Limitaciones: La validación de bytes mágicos solo confirma que el archivo comienza con la cabecera correcta, no garantiza que el contenido sea seguro. Los archivos políglotas pueden tener cabeceras de imagen válidas y código malicioso incrustado simultáneamente. Por lo tanto, la validación de bytes mágicos es necesaria pero no suficiente como defensa.
Recodificación de imágenes - La defensa más efectiva
Recodificar las imágenes subidas con una biblioteca del lado del servidor (decodificar y volver a codificar) es la defensa más efectiva. La recodificación descarta todos los datos que no son píxeles, eliminando cualquier código malicioso incrustado.
Principio: La biblioteca de imágenes decodifica el archivo a un array de píxeles sin procesar y luego lo recodifica al formato destino. En este proceso, cualquier dato no estándar (scripts incrustados, metadatos anómalos, cargas políglotas) se descarta.
Implementación con Sharp:
// Recodificar a WebP - elimina todas las amenazas potencialesconst safe = await sharp(uploadedBuffer) .resize(2000, 2000, { fit: 'inside', withoutEnlargement: true }) .webp({ quality: 80 }) .toBuffer();
Defensas adicionales:
- Limitar dimensiones máximas para prevenir bombas de descompresión (ej. máximo 4096×4096)
- Establecer timeout de procesamiento para evitar bucles infinitos causados por archivos maliciosos
- Procesar en entorno aislado (contenedor, Lambda)
Nota: La recodificación elimina los metadatos originales. Si necesita preservar información de copyright, escriba manualmente metadatos verificados después de la recodificación.
Sanitización de SVG - Resolver el foco de XSS
SVG es un formato vectorial basado en XML que puede contener JavaScript, referencias a recursos externos y manejadores de eventos, convirtiéndolo en un vector ideal para ataques XSS.
Elementos peligrosos en SVG:
<script>: Ejecución directa de JavaScript<foreignObject>: Incrustar HTML arbitrario- Atributos
on*: Manejadores de eventos (onclick, onload, etc.) xlink:href="javascript:...": Enlaces con protocolo JavaScript<use href="external.svg#id">: Referencias a recursos externos
Estrategias de sanitización:
- Usar bibliotecas como DOMPurify o svg-sanitizer para eliminar elementos y atributos peligrosos
- Enfoque de lista blanca: conservar solo elementos y atributos SVG conocidos como seguros
- Eliminar todos los atributos de manejadores de eventos (on*)
- Eliminar todos los elementos script y foreignObject
- Eliminar URLs con protocolo javascript:
Al servir SVG: Establecer Content-Type: image/svg+xml (no text/html); añadir cabecera Content-Security-Policy para restringir ejecución de scripts; considerar convertir SVG a PNG para fuentes no confiables.
ImageTragick y mitigación de vulnerabilidades en bibliotecas de procesamiento
ImageTragick (CVE-2016-3714) fue una vulnerabilidad crítica de ImageMagick que permitía ejecutar comandos arbitrarios mediante archivos de imagen especialmente diseñados. Vulnerabilidades similares se descubren periódicamente en otras bibliotecas de procesamiento de imágenes.
Principio de ImageTragick: El mecanismo de delegados de ImageMagick invoca comandos externos al procesar ciertos formatos. Los atacantes construyen archivos SVG o MVG que contienen comandos shell, que ImageMagick ejecuta durante el procesamiento.
Medidas de mitigación:
- Restricciones en policy.xml: Deshabilitar delegados y codificadores peligrosos (MVG, MSL, EPHEMERAL, URL, HTTPS)
- Usar bibliotecas alternativas: Sharp (basado en libvips) no usa mecanismo de delegados, tiene menor superficie de ataque
- Mantener actualizado: Actualizar las bibliotecas de procesamiento de imágenes a la última versión
- Aislamiento en sandbox: Ejecutar el procesamiento de imágenes en contenedores o Lambda, limitando acceso al sistema de archivos y red
Defensa general:
- Principio de mínimo privilegio: el proceso de procesamiento de imágenes no debe tener permisos de escritura en el sistema de archivos ni acceso a red
- Límites de recursos: establecer límites de memoria, CPU y tiempo
- Monitoreo de anomalías: alertar cuando el tiempo de procesamiento o uso de memoria sea anormalmente alto
Lista de verificación - Diseño seguro de carga de imágenes
Lista de verificación completa para construir un sistema seguro de carga de imágenes.
Antes de la carga (cliente):
- Limitar tamaño de archivo (aviso en frontend, obligatorio en backend)
- Restringir tipos de archivo permitidos (atributo accept)
- La validación del cliente es solo para mejorar la experiencia de usuario, no como medida de seguridad
Al recibir (servidor):
- Verificar que Content-Length no exceda el límite
- Verificar que los bytes mágicos coincidan con el formato declarado
- Rechazar formatos que no estén en la lista blanca
- Generar nombre de archivo aleatorio, no usar el proporcionado por el usuario
Al procesar:
- Recodificar con Sharp (el paso más crítico)
- Limitar dimensiones de salida (prevenir bombas de descompresión)
- Establecer timeout de procesamiento
- Ejecutar en entorno aislado
Al almacenar:
- Almacenar en dominio/bucket independiente (no el dominio de la aplicación)
- Establecer Content-Type correcto
- Prohibir permisos de ejecución
- Servir mediante CDN, no directamente desde el servidor de aplicación