Fix: Seguridad de Imágenes

This commit is contained in:
2026-01-30 15:23:50 -03:00
parent a5f501e88e
commit 8f0b9546d4

View File

@@ -78,11 +78,27 @@ public class ImageStorageService : IImageStorageService
{ {
string hex = BitConverter.ToString(headerBytes.Take(8).ToArray()); string hex = BitConverter.ToString(headerBytes.Take(8).ToArray());
_logger.LogWarning("Firma de archivo inválida para {Extension}: {HexBytes}", ext, hex); _logger.LogWarning("Firma de archivo inválida para {Extension}: {HexBytes}", ext, hex);
throw new Exception($"El archivo parece corrupto o tiene una firma inválida ({hex}). El sistema acepta JPG, PNG y WEBP reales."); throw new Exception($"El archivo parece corrupto o tiene una firma inválida ({hex}).");
} }
} }
try try
{ {
// 4. PREVENCIÓN DoS: Validar dimensiones sin cargar la imagen completa en memoria
// Esto evita que una imagen de 1KB que dice ser de 50000x50000px cuelgue el servidor
using (var stream = file.OpenReadStream())
{
var info = await Image.IdentifyAsync(stream);
if (info == null) throw new Exception("No se pudo identificar el formato de la imagen.");
const int MaxDimension = 5000; // 5000px es más que suficiente para un aviso
if (info.Width > MaxDimension || info.Height > MaxDimension)
{
_logger.LogWarning("Intento de subir imagen con dimensiones excesivas: {W}x{H}", info.Width, info.Height);
throw new Exception($"Las dimensiones de la imagen ({info.Width}x{info.Height}) exceden el límite de seguridad de {MaxDimension}px.");
}
}
// 1. Definir rutas // 1. Definir rutas
var uploadFolder = Path.Combine(_env.WebRootPath, "uploads", "ads", adId.ToString()); var uploadFolder = Path.Combine(_env.WebRootPath, "uploads", "ads", adId.ToString());
if (!Directory.Exists(uploadFolder)) Directory.CreateDirectory(uploadFolder); if (!Directory.Exists(uploadFolder)) Directory.CreateDirectory(uploadFolder);
@@ -94,7 +110,7 @@ public class ImageStorageService : IImageStorageService
var filePath = Path.Combine(uploadFolder, fileName); var filePath = Path.Combine(uploadFolder, fileName);
var thumbPath = Path.Combine(uploadFolder, thumbName); var thumbPath = Path.Combine(uploadFolder, thumbName);
// 2. Cargar y Procesar con ImageSharp // 2. Cargar y Procesar con ImageSharp (Aquí ya es seguro porque validamos dimensiones e identidad)
using (var image = await Image.LoadAsync(file.OpenReadStream())) using (var image = await Image.LoadAsync(file.OpenReadStream()))
{ {
// A. Guardar imagen principal (Optimized: Max width 1280px) // A. Guardar imagen principal (Optimized: Max width 1280px)
@@ -102,22 +118,21 @@ public class ImageStorageService : IImageStorageService
{ {
image.Mutate(x => x.Resize(new ResizeOptions image.Mutate(x => x.Resize(new ResizeOptions
{ {
Size = new Size(1280, 0), // 0 mantiene aspect ratio Size = new Size(1280, 0),
Mode = ResizeMode.Max Mode = ResizeMode.Max
})); }));
} }
await image.SaveAsJpegAsync(filePath); await image.SaveAsJpegAsync(filePath);
// B. Generar Thumbnail (Max width 400px para grillas) // B. Generar Thumbnail
image.Mutate(x => x.Resize(new ResizeOptions image.Mutate(x => x.Resize(new ResizeOptions
{ {
Size = new Size(400, 300), Size = new Size(400, 300),
Mode = ResizeMode.Crop // Recorte inteligente para que queden parejitas Mode = ResizeMode.Crop
})); }));
await image.SaveAsJpegAsync(thumbPath); await image.SaveAsJpegAsync(thumbPath);
} }
// Retornar ruta relativa web de la imagen principal
return $"/uploads/ads/{adId}/{fileName}"; return $"/uploads/ads/{adId}/{fileName}";
} }
catch (Exception ex) catch (Exception ex)