diff --git a/NuGet.config b/NuGet.config index dfd4d34..5ccb822 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,10 +1,10 @@ - - - - + + + + diff --git a/src/Unic.ErrorManager.Core/Controls/BaseError.cs b/src/Unic.ErrorManager.Core/Controls/BaseError.cs index 30aea00..a3dfe78 100644 --- a/src/Unic.ErrorManager.Core/Controls/BaseError.cs +++ b/src/Unic.ErrorManager.Core/Controls/BaseError.cs @@ -87,16 +87,14 @@ protected string SettingsKey /// The object that contains the event data. protected override void OnLoad(EventArgs e) { - // if a hostname allow list is defined and the host header value from the request doesn't appear in this list, the rest of this method isn't executed to prevent potential SSRF - var hostnameAllowList = Settings.GetSetting(Definitions.Constants.HostnameAllowListSetting); - if (!string.IsNullOrWhiteSpace(hostnameAllowList)) + // validate the host header against allowed hostnames to prevent SSRF via host header injection + var requestHost = Request.Headers["host"] ?? string.Empty; + var requestHostWithoutPort = requestHost.Split(':')[0]; + if (!this.IsHostnameAllowed(requestHostWithoutPort)) { - if (!hostnameAllowList.Split(';').Any(h => h.Equals(Request.Headers["host"]))) - { - this.Response.StatusCode = 403; - this.Response.Write("403 Forbidden"); - return; - } + this.Response.StatusCode = 403; + this.Response.Write("403 Forbidden"); + return; } // add support to all versions of tls @@ -168,6 +166,16 @@ protected override void OnLoad(EventArgs e) } } + // validate the constructed URL host against allowed hostnames before making the request + var constructedUri = new Uri(url.ToString()); + if (!this.IsHostnameAllowed(constructedUri.Host)) + { + Log.Error($"ErrorManager: Constructed URL host '{constructedUri.Host}' is not allowed. Aborting request.", this); + this.Response.StatusCode = 403; + this.Response.Write("403 Forbidden"); + return; + } + // parse the page HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url.ToString()); @@ -190,7 +198,6 @@ protected override void OnLoad(EventArgs e) this.HandleBasicAuthentication(request); HttpWebResponse response = null; - bool hasAddedValidationCallback = false; try { @@ -208,8 +215,7 @@ protected override void OnLoad(EventArgs e) if (Settings.GetBoolSetting("ErrorManager.IgnoreInvalidSSLCertificates", false)) { - ServicePointManager.ServerCertificateValidationCallback += ValidateRemoteCertificate; - hasAddedValidationCallback = true; + request.ServerCertificateValidationCallback = ValidateRemoteCertificate; } // do the request @@ -224,14 +230,6 @@ protected override void OnLoad(EventArgs e) Log.Error($"ErrorManager : {ex.Message}. Request URL: {url}. ", ex, this); } - finally - { - // Remove the custom RemoteCertificateValidationCallback due to the global nature of the ServicePointManager - if (hasAddedValidationCallback) - { - ServicePointManager.ServerCertificateValidationCallback -= ValidateRemoteCertificate; - } - } // outputs the page if (response != null) @@ -276,6 +274,47 @@ protected virtual void AddUserAgentHeader(HttpWebRequest request) request.UserAgent = userAgent; } + private bool IsHostnameAllowed(string hostname) + { + if (string.IsNullOrWhiteSpace(hostname)) return false; + + var hostnameAllowList = Settings.GetSetting(Definitions.Constants.HostnameAllowListSetting); + var useSitecoreSiteHostnames = Settings.GetBoolSetting(Definitions.Constants.ValidateSitecoreSiteHostnamesSetting, true); + + // check the optional explicit allow list + if (!string.IsNullOrWhiteSpace(hostnameAllowList)) + { + if (hostnameAllowList.Split(';').Any(h => h.Equals(hostname, StringComparison.OrdinalIgnoreCase))) + { + return true; + } + } + + // check hostnames from all configured Sitecore sites + if (useSitecoreSiteHostnames) + { + foreach (var siteInfo in SiteContextFactory.Sites) + { + if (string.IsNullOrWhiteSpace(siteInfo.HostName)) continue; + + // SiteInfo.HostName can contain pipe-separated values + var siteHostnames = siteInfo.HostName.Split('|'); + if (siteHostnames.Any(h => h.Equals(hostname, StringComparison.OrdinalIgnoreCase))) + { + return true; + } + + if (!string.IsNullOrWhiteSpace(siteInfo.TargetHostName) + && siteInfo.TargetHostName.Equals(hostname, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + + return false; + } + private void HandleHeaderForwarding(HttpWebRequest request) { var headers = Settings.GetSetting("ErrorManager.ForwardedHeaders"); diff --git a/src/Unic.ErrorManager.Core/Definitions/Constants.cs b/src/Unic.ErrorManager.Core/Definitions/Constants.cs index 81d4cd3..18422e8 100644 --- a/src/Unic.ErrorManager.Core/Definitions/Constants.cs +++ b/src/Unic.ErrorManager.Core/Definitions/Constants.cs @@ -15,5 +15,7 @@ public static class Constants public static string EnableAgentHeaderCheckSetting = "ErrorManager.EnableAgentHeaderCheck"; public static string HostnameAllowListSetting = "ErrorManager.HostnameAllowList"; + + public static string ValidateSitecoreSiteHostnamesSetting = "ErrorManager.ValidateSitecoreSiteHostnames"; } } diff --git a/src/Unic.ErrorManager.Core/Unic.ErrorManager.Core.csproj b/src/Unic.ErrorManager.Core/Unic.ErrorManager.Core.csproj index e88bd7c..8cc7320 100644 --- a/src/Unic.ErrorManager.Core/Unic.ErrorManager.Core.csproj +++ b/src/Unic.ErrorManager.Core/Unic.ErrorManager.Core.csproj @@ -12,7 +12,7 @@ Properties Unic.ErrorManager.Core Unic.ErrorManager.Core - v4.5 + v4.8 512 diff --git a/src/Unic.ErrorManager.Website/App_Config/Include/01_modules/Unic.ErrorManager.config b/src/Unic.ErrorManager.Website/App_Config/Include/01_modules/Unic.ErrorManager.config index 6bf0ea4..59939d9 100644 --- a/src/Unic.ErrorManager.Website/App_Config/Include/01_modules/Unic.ErrorManager.config +++ b/src/Unic.ErrorManager.Website/App_Config/Include/01_modules/Unic.ErrorManager.config @@ -105,6 +105,25 @@ --> + + + + + + diff --git a/src/Unic.ErrorManager.Website/Unic.ErrorManager.Website.csproj b/src/Unic.ErrorManager.Website/Unic.ErrorManager.Website.csproj index 1860d7a..e682594 100644 --- a/src/Unic.ErrorManager.Website/Unic.ErrorManager.Website.csproj +++ b/src/Unic.ErrorManager.Website/Unic.ErrorManager.Website.csproj @@ -15,7 +15,7 @@ Properties Unic.ErrorManager.Website Unic.ErrorManager.Website - v4.5 + v4.8 false @@ -68,10 +68,8 @@ - - @@ -79,7 +77,6 @@ - @@ -121,6 +118,13 @@ + + + web.config + + + web.config + diff --git a/src/Unic.ErrorManager.Website/web.Debug.config b/src/Unic.ErrorManager.Website/web.Debug.config new file mode 100644 index 0000000..fae9cfe --- /dev/null +++ b/src/Unic.ErrorManager.Website/web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/Unic.ErrorManager.Website/web.Release.config b/src/Unic.ErrorManager.Website/web.Release.config new file mode 100644 index 0000000..da6e960 --- /dev/null +++ b/src/Unic.ErrorManager.Website/web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Unic.ErrorManager.Website/web.config b/src/Unic.ErrorManager.Website/web.config new file mode 100644 index 0000000..a3731a0 --- /dev/null +++ b/src/Unic.ErrorManager.Website/web.config @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file