using System.Net; using System.Net.Sockets; namespace Deal.Infrastructure.Integrations; /// /// Расширения для SSRF-гейта интеграций (проверка приватности адреса). /// internal static class UriExtensions { /// /// Проверяет, указывает ли URL на приватный/loopback/link-local адрес (SSRF-гейт). /// Распознаются IP-литералы (IPv4/IPv6) и имя localhost; DNS-имена считаются публичными /// (полный egress-контроль с резолвом выполняется на сетевом периметре). /// /// Абсолютный http(s)-адрес. /// True — адрес приватный/локальный (HTTP к нему запрещён). public static bool IsPrivateEndpoint(this Uri uri) { string host = uri.Host; if (string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase)) { return true; } if (!IPAddress.TryParse(host, out IPAddress? address)) { return false; // DNS-имя — резолв вне этого слоя } if (address.IsIPv4MappedToIPv6) { address = address.MapToIPv4(); } if (IPAddress.IsLoopback(address)) { return true; } if (address.AddressFamily == AddressFamily.InterNetwork) { byte[] bytes = address.GetAddressBytes(); return bytes[0] == 10 || (bytes[0] == 172 && bytes[1] is >= 16 and <= 31) || (bytes[0] == 192 && bytes[1] == 168) || bytes[0] == 169 && bytes[1] == 254 // link-local (включая 169.254.169.254 metadata) || bytes[0] == 0; } // IPv6: уникальные локальные (fc00::/7) и link-local (fe80::/10). byte[] v6 = address.GetAddressBytes(); return (v6[0] & 0xFE) == 0xFC || (v6[0] == 0xFE && (v6[1] & 0xC0) == 0x80); } }