using System.Net; namespace ChatbotApi.Services { public static class UrlSecurity { // Lista de rangos de IP privados y reservados private static readonly List<(IPAddress Address, int PrefixLength)> PrivateRanges = new List<(IPAddress, int)> { (IPAddress.Parse("10.0.0.0"), 8), (IPAddress.Parse("172.16.0.0"), 12), (IPAddress.Parse("192.168.0.0"), 16), (IPAddress.Parse("127.0.0.0"), 8), (IPAddress.Parse("0.0.0.0"), 8), (IPAddress.Parse("::1"), 128) // IPv6 Loopback }; /// /// Verifica si una URL es segura para ser visitada por el bot. /// Bloquea IPs privadas, locales y esquemas no HTTP/HTTPS. /// public static async Task IsSafeUrlAsync(string url) { if (string.IsNullOrWhiteSpace(url)) return false; // 1. Validar formato de URL y esquema (solo http/https) if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uriResult)) return false; if (uriResult.Scheme != Uri.UriSchemeHttp && uriResult.Scheme != Uri.UriSchemeHttps) return false; // 2. Si es eldia.com, confiamos (Whitelisting explícito para el dominio principal) if (uriResult.Host.EndsWith("eldia.com", StringComparison.OrdinalIgnoreCase)) return true; // 3. Resolución DNS para verificar que no apunte a una IP local (SSRF) try { var ipAddresses = await Dns.GetHostAddressesAsync(uriResult.Host); foreach (var ip in ipAddresses) { if (IsPrivateIp(ip)) { // Log (opcional): Intento de acceso a IP privada: uriResult.Host -> ip return false; } } } catch { // Si falla el DNS, denegamos por seguridad return false; } return true; } private static bool IsPrivateIp(IPAddress ip) { if (IPAddress.IsLoopback(ip)) return true; // Convertir a bytes para comparar rangos byte[] ipBytes = ip.GetAddressBytes(); // Manejo simplificado para IPv4 Mapped to IPv6 if (ip.IsIPv4MappedToIPv6) { ip = ip.MapToIPv4(); ipBytes = ip.GetAddressBytes(); } // Solo verificamos rangos privados en IPv4 por simplicidad y riesgo común if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { foreach (var (baseIp, prefixLength) in PrivateRanges) { if (baseIp.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && IsInSubnet(ip, baseIp, prefixLength)) { return true; } } } return false; } private static bool IsInSubnet(IPAddress address, IPAddress subnetMask, int prefixLength) { var ipBytes = address.GetAddressBytes(); var maskBytes = subnetMask.GetAddressBytes(); // Calcular máscara de bits var bits = new System.Collections.BitArray(ipBytes.Length * 8, false); // Lógica simplificada de comparación de bits para netmask... // Para mantener el código limpio y funcional sin librerías externas complejas: // Chequeo rápido de los 3 rangos clásicos de RFC1918 if (ipBytes[0] == 10) return true; // 10.0.0.0/8 if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31) return true; // 172.16.0.0/12 if (ipBytes[0] == 192 && ipBytes[1] == 168) return true; // 192.168.0.0/16 return false; } } }