Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions NuGet.config
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageRestore>
<!-- Opts out of both Automatic Package Restore and MSBuild-Integrated Package Restore -->
<add key="enabled" value="False" />
<!-- Opts out of Automatic Package Restore in Visual Studio -->
<add key="automatic" value="False" />
<!-- Enable Automatic + MSBuild-Integrated Package Restore -->
<add key="enabled" value="True" />
<!-- Enable Automatic Package Restore in Visual Studio -->
<add key="automatic" value="True" />
</packageRestore>
<packageSources>
<clear />
Expand Down
79 changes: 59 additions & 20 deletions src/Unic.ErrorManager.Core/Controls/BaseError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,14 @@ protected string SettingsKey
/// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param>
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
Expand Down Expand Up @@ -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());

Expand All @@ -190,7 +198,6 @@ protected override void OnLoad(EventArgs e)
this.HandleBasicAuthentication(request);

HttpWebResponse response = null;
bool hasAddedValidationCallback = false;

try
{
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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");
Expand Down
2 changes: 2 additions & 0 deletions src/Unic.ErrorManager.Core/Definitions/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}
2 changes: 1 addition & 1 deletion src/Unic.ErrorManager.Core/Unic.ErrorManager.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Unic.ErrorManager.Core</RootNamespace>
<AssemblyName>Unic.ErrorManager.Core</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,25 @@
-->
<setting name="NoLicenseUrl" set:value="/sitecore modules/Web/Error Manager/service/nolicense.aspx" />

<!-- VALIDATE SITECORE SITE HOSTNAMES
If true, hostnames configured on Sitecore site definitions (HostName and
TargetHostName attributes) are treated as allowed for error page requests.
This is the primary SSRF protection mechanism and is enabled by default.
Default value: true
-->
<setting name="ErrorManager.ValidateSitecoreSiteHostnames" value="true" />

<!-- HOSTNAME ALLOW LIST (OPTIONAL)
Semicolon-separated list of additional allowed hostnames for error page
requests, checked alongside the Sitecore site hostnames above.
Port is not required — the check strips port suffixes automatically.
Use this to allow hostnames that are not configured on any Sitecore site
definition (e.g. load balancer or CDN hostnames).
Example: "cdn.example.com;lb.internal.example.com"
Default value: ""
-->
<setting name="ErrorManager.HostnameAllowList" value="" />

<!-- BOT SEETINGS
Defines the Bot for the redirect request
-->
Expand Down
12 changes: 8 additions & 4 deletions src/Unic.ErrorManager.Website/Unic.ErrorManager.Website.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Unic.ErrorManager.Website</RootNamespace>
<AssemblyName>Unic.ErrorManager.Website</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<UseIISExpress>false</UseIISExpress>
<FileUpgradeFlags>
</FileUpgradeFlags>
Expand Down Expand Up @@ -68,18 +68,15 @@
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Drawing" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Web.Services" />
<Reference Include="System.EnterpriseServices" />
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
Expand Down Expand Up @@ -121,6 +118,13 @@
<Content Include="packages.config" />
<None Include="Properties\PublishProfiles\LocalDeploy.pubxml" />
<None Include="Unic.ErrorManager.Website.nuspec" />
<Content Include="web.config" />
<None Include="web.Debug.config">
<DependentUpon>web.config</DependentUpon>
</None>
<None Include="web.Release.config">
<DependentUpon>web.config</DependentUpon>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Unic.ErrorManager.Core\Unic.ErrorManager.Core.csproj">
Expand Down
30 changes: 30 additions & 0 deletions src/Unic.ErrorManager.Website/web.Debug.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>

<!-- For more information on using web.config transformation visit https://go.microsoft.com/fwlink/?LinkId=125889 -->

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--
In the example below, the "SetAttributes" transform will change the value of
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
finds an attribute "name" that has a value of "MyDB".

<connectionStrings>
<add name="MyDB"
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>
-->
<system.web>
<!--
In the example below, the "Replace" transform will replace the entire
<customErrors> section of your web.config file.
Note that because there is only one customErrors section under the
<system.web> node, there is no need to use the "xdt:Locator" attribute.

<customErrors defaultRedirect="GenericError.htm"
mode="RemoteOnly" xdt:Transform="Replace">
<error statusCode="500" redirect="InternalError.htm"/>
</customErrors>
-->
</system.web>
</configuration>
31 changes: 31 additions & 0 deletions src/Unic.ErrorManager.Website/web.Release.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>

<!-- For more information on using web.config transformation visit https://go.microsoft.com/fwlink/?LinkId=125889 -->

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--
In the example below, the "SetAttributes" transform will change the value of
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
finds an attribute "name" that has a value of "MyDB".

<connectionStrings>
<add name="MyDB"
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>
-->
<system.web>
<compilation xdt:Transform="RemoveAttributes(debug)" />
<!--
In the example below, the "Replace" transform will replace the entire
<customErrors> section of your web.config file.
Note that because there is only one customErrors section under the
<system.web> node, there is no need to use the "xdt:Locator" attribute.

<customErrors defaultRedirect="GenericError.htm"
mode="RemoteOnly" xdt:Transform="Replace">
<error statusCode="500" redirect="InternalError.htm"/>
</customErrors>
-->
</system.web>
</configuration>
15 changes: 15 additions & 0 deletions src/Unic.ErrorManager.Website/web.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<configuration>
<!--
For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.

The following attributes can be set on the <httpRuntime> tag.
<system.Web>
<httpRuntime targetFramework="4.8" />
</system.Web>
-->
<system.web>
<compilation debug="true" targetFramework="4.8"/>
<httpRuntime targetFramework="4.5"/>
</system.web>
</configuration>