diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs
new file mode 100644
index 0000000000..165d636a6d
--- /dev/null
+++ b/src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+using System.Xml.XPath;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.IO;
+using Umbraco.Core.Services;
+
+namespace Umbraco.Web.HealthCheck.Checks.Security
+{
+ public abstract class BaseHttpHeaderCheck : HealthCheck
+ {
+ private readonly ILocalizedTextService _textService;
+
+ private const string SetHeaderInConfigAction = "setHeaderInConfig";
+
+ private readonly string _header;
+ private readonly string _value;
+ private readonly string _localizedTextPrefix;
+ private readonly bool _metaTagOptionAvailable;
+
+ public BaseHttpHeaderCheck(HealthCheckContext healthCheckContext,
+ string header, string value, string localizedTextPrefix, bool metaTagOptionAvailable) : base(healthCheckContext)
+ {
+ _textService = healthCheckContext.ApplicationContext.Services.TextService;
+
+ _header = header;
+ _value = value;
+ _localizedTextPrefix = localizedTextPrefix;
+ _metaTagOptionAvailable = metaTagOptionAvailable;
+ }
+
+ ///
+ /// Get the status for this health check
+ ///
+ ///
+ public override IEnumerable GetStatus()
+ {
+ //return the statuses
+ return new[] { CheckForHeader() };
+ }
+
+ ///
+ /// Executes the action and returns it's status
+ ///
+ ///
+ ///
+ public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
+ {
+ switch (action.Alias)
+ {
+ case SetHeaderInConfigAction:
+ return SetHeaderInConfig();
+ default:
+ throw new InvalidOperationException("HTTP Header action requested is either not executable or does not exist");
+ }
+ }
+
+ protected HealthCheckStatus CheckForHeader()
+ {
+ var message = string.Empty;
+ var success = false;
+ var url = HealthCheckContext.HttpContext.Request.Url;
+
+ // Access the site home page and check for the click-jack protection header or meta tag
+ var serverVariables = HealthCheckContext.HttpContext.Request.ServerVariables;
+ var useSsl = GlobalSettings.UseSSL || serverVariables["SERVER_PORT"] == "443";
+ var address = string.Format("http{0}://{1}:{2}", useSsl ? "s" : "", url.Host.ToLower(), url.Port);
+ var request = WebRequest.Create(address);
+ request.Method = "GET";
+ try
+ {
+ var response = request.GetResponse();
+
+ // Check first for header
+ success = DoHttpHeadersContainHeader(response);
+
+ // If not found, and available, check for meta-tag
+ if (success == false && _metaTagOptionAvailable)
+ {
+ success = DoMetaTagsContainKeyForHeader(response);
+ }
+
+ message = success
+ ? _textService.Localize(string.Format("healthcheck/{0}CheckHeaderFound", _localizedTextPrefix))
+ : _textService.Localize(string.Format("healthcheck/{0}CheckHeaderNotFound", _localizedTextPrefix));
+ }
+ catch (Exception ex)
+ {
+ message = _textService.Localize("healthcheck/healthCheckInvalidUrl", new[] { address, ex.Message });
+ }
+
+ var actions = new List();
+ if (success == false)
+ {
+ actions.Add(new HealthCheckAction(SetHeaderInConfigAction, Id)
+ {
+ Name = _textService.Localize("healthcheck/setHeaderInConfig"),
+ Description = _textService.Localize(string.Format("healthcheck/{0}SetHeaderInConfigDescription", _localizedTextPrefix))
+ });
+ }
+
+ return
+ new HealthCheckStatus(message)
+ {
+ ResultType = success ? StatusResultType.Success : StatusResultType.Error,
+ Actions = actions
+ };
+ }
+
+ private bool DoHttpHeadersContainHeader(WebResponse response)
+ {
+ return response.Headers.AllKeys.Contains(_header);
+ }
+
+ private bool DoMetaTagsContainKeyForHeader(WebResponse response)
+ {
+ using (var stream = response.GetResponseStream())
+ {
+ if (stream == null) return false;
+ using (var reader = new StreamReader(stream))
+ {
+ var html = reader.ReadToEnd();
+ var metaTags = ParseMetaTags(html);
+ return metaTags.ContainsKey(_header);
+ }
+ }
+ }
+
+ private static Dictionary ParseMetaTags(string html)
+ {
+ var regex = new Regex("()
+ .ToDictionary(m => m.Groups[1].Value, m => m.Groups[2].Value);
+ }
+
+ private HealthCheckStatus SetHeaderInConfig()
+ {
+ var errorMessage = string.Empty;
+ var success = SaveHeaderToConfigFile(out errorMessage);
+
+ if (success)
+ {
+ return
+ new HealthCheckStatus(_textService.Localize(string.Format("healthcheck/{0}SetHeaderInConfigSuccess", _localizedTextPrefix)))
+ {
+ ResultType = StatusResultType.Success
+ };
+ }
+
+ return
+ new HealthCheckStatus(_textService.Localize("healthcheck/setHeaderInConfigError", new [] { errorMessage }))
+ {
+ ResultType = StatusResultType.Error
+ };
+ }
+
+ private bool SaveHeaderToConfigFile(out string errorMessage)
+ {
+ try
+ {
+ // There don't look to be any useful classes defined in https://msdn.microsoft.com/en-us/library/system.web.configuration(v=vs.110).aspx
+ // for working with the customHeaders section, so working with the XML directly.
+ var configFile = IOHelper.MapPath("~/Web.config");
+ var doc = XDocument.Load(configFile);
+ var systemWebServerElement = doc.XPathSelectElement("/configuration/system.webServer");
+ var httpProtocolElement = systemWebServerElement.Element("httpProtocol");
+ if (httpProtocolElement == null)
+ {
+ httpProtocolElement = new XElement("httpProtocol");
+ systemWebServerElement.Add(httpProtocolElement);
+ }
+
+ var customHeadersElement = httpProtocolElement.Element("customHeaders");
+ if (customHeadersElement == null)
+ {
+ customHeadersElement = new XElement("customHeaders");
+ httpProtocolElement.Add(customHeadersElement);
+ }
+
+ var removeHeaderElement = customHeadersElement.Elements("remove")
+ .SingleOrDefault(x => x.Attribute("name") != null &&
+ x.Attribute("name").Value == _value);
+ if (removeHeaderElement == null)
+ {
+ removeHeaderElement = new XElement("remove");
+ removeHeaderElement.Add(new XAttribute("name", _header));
+ customHeadersElement.Add(removeHeaderElement);
+ }
+
+ var addHeaderElement = customHeadersElement.Elements("add")
+ .SingleOrDefault(x => x.Attribute("name") != null &&
+ x.Attribute("name").Value == _header);
+ if (addHeaderElement == null)
+ {
+ addHeaderElement = new XElement("add");
+ addHeaderElement.Add(new XAttribute("name", _header));
+ addHeaderElement.Add(new XAttribute("value", _value));
+ customHeadersElement.Add(addHeaderElement);
+ }
+
+ doc.Save(configFile);
+
+ errorMessage = string.Empty;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ errorMessage = ex.Message;
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs
index 54f72dc88a..34ba7bee19 100644
--- a/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs
+++ b/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs
@@ -1,217 +1,15 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Text.RegularExpressions;
-using System.Xml.Linq;
-using System.Xml.XPath;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.IO;
-using Umbraco.Core.Services;
-
-namespace Umbraco.Web.HealthCheck.Checks.Security
+namespace Umbraco.Web.HealthCheck.Checks.Security
{
[HealthCheck(
"ED0D7E40-971E-4BE8-AB6D-8CC5D0A6A5B0",
"Click-Jacking Protection",
Description = "Checks if your site is allowed to be IFRAMEd by another site and thus would be susceptible to click-jacking.",
Group = "Security")]
- public class ClickJackingCheck : HealthCheck
+ public class ClickJackingCheck : BaseHttpHeaderCheck
{
- private readonly ILocalizedTextService _textService;
-
- private const string SetFrameOptionsHeaderInConfigActiobn = "setFrameOptionsHeaderInConfig";
-
- private const string XFrameOptionsHeader = "X-Frame-Options";
- private const string XFrameOptionsValue = "sameorigin"; // Note can't use "deny" as that would prevent Umbraco itself using IFRAMEs
-
- public ClickJackingCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext)
+ public ClickJackingCheck(HealthCheckContext healthCheckContext)
+ : base(healthCheckContext, "X-Frame-Options", "sameorigin", "clickJacking", true)
{
- _textService = healthCheckContext.ApplicationContext.Services.TextService;
- }
-
- ///
- /// Get the status for this health check
- ///
- ///
- public override IEnumerable GetStatus()
- {
- //return the statuses
- return new[] { CheckForFrameOptionsHeader() };
- }
-
- ///
- /// Executes the action and returns it's status
- ///
- ///
- ///
- public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
- {
- switch (action.Alias)
- {
- case SetFrameOptionsHeaderInConfigActiobn:
- return SetFrameOptionsHeaderInConfig();
- default:
- throw new InvalidOperationException("HttpsCheck action requested is either not executable or does not exist");
- }
- }
-
- private HealthCheckStatus CheckForFrameOptionsHeader()
- {
- var message = string.Empty;
- var success = false;
- var url = HealthCheckContext.HttpContext.Request.Url;
-
- // Access the site home page and check for the click-jack protection header or meta tag
- var serverVariables = HealthCheckContext.HttpContext.Request.ServerVariables;
- var useSsl = GlobalSettings.UseSSL || serverVariables["SERVER_PORT"] == "443";
- var address = string.Format("http{0}://{1}:{2}", useSsl ? "s" : "", url.Host.ToLower(), url.Port);
- var request = WebRequest.Create(address);
- request.Method = "GET";
- try
- {
- var response = request.GetResponse();
-
- // Check first for header
- success = DoHeadersContainFrameOptions(response);
-
- // If not found, check for meta-tag
- if (success == false)
- {
- success = DoMetaTagsContainFrameOptions(response);
- }
-
- message = success
- ? _textService.Localize("healthcheck/clickJackingCheckHeaderFound")
- : _textService.Localize("healthcheck/clickJackingCheckHeaderNotFound");
- }
- catch (Exception ex)
- {
- message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { address, ex.Message });
- }
-
- var actions = new List();
- if (success == false)
- {
- actions.Add(new HealthCheckAction(SetFrameOptionsHeaderInConfigActiobn, Id)
- {
- Name = _textService.Localize("healthcheck/clickJackingSetHeaderInConfig"),
- Description = _textService.Localize("healthcheck/clickJackingSetHeaderInConfigDescription")
- });
- }
-
- return
- new HealthCheckStatus(message)
- {
- ResultType = success ? StatusResultType.Success : StatusResultType.Error,
- Actions = actions
- };
- }
-
- private static bool DoHeadersContainFrameOptions(WebResponse response)
- {
- return response.Headers.AllKeys.Contains(XFrameOptionsHeader);
- }
-
- private static bool DoMetaTagsContainFrameOptions(WebResponse response)
- {
- using (var stream = response.GetResponseStream())
- {
- if (stream == null) return false;
- using (var reader = new StreamReader(stream))
- {
- var html = reader.ReadToEnd();
- var metaTags = ParseMetaTags(html);
- return metaTags.ContainsKey(XFrameOptionsHeader);
- }
- }
- }
-
- private static Dictionary ParseMetaTags(string html)
- {
- var regex = new Regex("()
- .ToDictionary(m => m.Groups[1].Value, m => m.Groups[2].Value);
- }
-
- private HealthCheckStatus SetFrameOptionsHeaderInConfig()
- {
- var errorMessage = string.Empty;
- var success = SaveHeaderToConfigFile(out errorMessage);
-
- if (success)
- {
- return
- new HealthCheckStatus(_textService.Localize("healthcheck/clickJackingSetHeaderInConfigSuccess"))
- {
- ResultType = StatusResultType.Success
- };
- }
-
- return
- new HealthCheckStatus(_textService.Localize("healthcheck/clickJackingSetHeaderInConfigError", new [] { errorMessage }))
- {
- ResultType = StatusResultType.Error
- };
- }
-
- private static bool SaveHeaderToConfigFile(out string errorMessage)
- {
- try
- {
- // There don't look to be any useful classes defined in https://msdn.microsoft.com/en-us/library/system.web.configuration(v=vs.110).aspx
- // for working with the customHeaders section, so working with the XML directly.
- var configFile = IOHelper.MapPath("~/Web.config");
- var doc = XDocument.Load(configFile);
- var systemWebServerElement = doc.XPathSelectElement("/configuration/system.webServer");
- var httpProtocolElement = systemWebServerElement.Element("httpProtocol");
- if (httpProtocolElement == null)
- {
- httpProtocolElement = new XElement("httpProtocol");
- systemWebServerElement.Add(httpProtocolElement);
- }
-
- var customHeadersElement = httpProtocolElement.Element("customHeaders");
- if (customHeadersElement == null)
- {
- customHeadersElement = new XElement("customHeaders");
- httpProtocolElement.Add(customHeadersElement);
- }
-
- var removeHeaderElement = customHeadersElement.Elements("remove")
- .SingleOrDefault(x => x.Attribute("name") != null &&
- x.Attribute("name").Value == XFrameOptionsHeader);
- if (removeHeaderElement == null)
- {
- removeHeaderElement = new XElement("remove");
- removeHeaderElement.Add(new XAttribute("name", XFrameOptionsHeader));
- customHeadersElement.Add(removeHeaderElement);
- }
-
- var addHeaderElement = customHeadersElement.Elements("add")
- .SingleOrDefault(x => x.Attribute("name") != null &&
- x.Attribute("name").Value == XFrameOptionsHeader);
- if (addHeaderElement == null)
- {
- addHeaderElement = new XElement("add");
- addHeaderElement.Add(new XAttribute("name", XFrameOptionsHeader));
- addHeaderElement.Add(new XAttribute("value", XFrameOptionsValue));
- customHeadersElement.Add(addHeaderElement);
- }
-
- doc.Save(configFile);
-
- errorMessage = string.Empty;
- return true;
- }
- catch (Exception ex)
- {
- errorMessage = ex.Message;
- return false;
- }
}
}
}
diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/NoSniffCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/NoSniffCheck.cs
new file mode 100644
index 0000000000..4982d9f272
--- /dev/null
+++ b/src/Umbraco.Web/HealthCheck/Checks/Security/NoSniffCheck.cs
@@ -0,0 +1,15 @@
+namespace Umbraco.Web.HealthCheck.Checks.Security
+{
+ [HealthCheck(
+ "1CF27DB3-EFC0-41D7-A1BB-EA912064E071",
+ "Content/MIME Sniffing Protection",
+ Description = "Checks that your site contains a header used to protect against MIME sniffing vulnerabilities.",
+ Group = "Security")]
+ public class NoSniffCheck : BaseHttpHeaderCheck
+ {
+ public NoSniffCheck(HealthCheckContext healthCheckContext)
+ : base(healthCheckContext, "X-Content-Type-Options", "nosniff", "noSniff", false)
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 0b5652e509..d6d622315f 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -325,6 +325,8 @@
+
+