add content security policy
This commit is contained in:
10
Readme.md
10
Readme.md
@@ -2,7 +2,7 @@
|
||||
|
||||
<img src="Hat.png" width="350px"/>
|
||||
|
||||
HardHat is a set of .net core middleware that adds various headers to help protect your site from vulnerablities. Inspired by [helmetJS](https://helmetjs.github.io). Currently in beta, Content Security Policy, Unit tests, documentation due before 1.0.0. Netherless this should work fine.
|
||||
HardHat is a set of .net core middleware that adds various headers to help protect your site from vulnerabilities. Inspired by [helmetJS](https://helmetjs.github.io). Currently in beta, Content Security Policy, Unit tests, documentation due before 1.0.0. Even still, this should work fine.
|
||||
|
||||
|
||||
In short this allows:
|
||||
@@ -22,6 +22,14 @@ In short this allows:
|
||||
app.UseIENoOpen(); // don't allow old ie to open files in the context of your site
|
||||
app.UseNoMimeSniff(); // prevent MIME sniffing https://en.wikipedia.org/wiki/Content_sniffing
|
||||
app.UseCrossSiteScriptingFilters(); //add headers to have the browsers auto detect and block some xss attacks
|
||||
app.UseContentSecurityPolicy(
|
||||
new ContentSecurityPolicyBuilder()
|
||||
.WithDefaultSource(CSPConstants.Self)
|
||||
.WithImageSource("http://images.mysite.com")
|
||||
.WithFontSource(CSPConstants.Self)
|
||||
.WithFrameAncestors(CSPConstants.None)
|
||||
.BuildPolicy()
|
||||
);
|
||||
...
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
|
||||
@@ -43,14 +43,29 @@ namespace HardHat.Example
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
}
|
||||
app.UseDnsPrefetch(allow: false);
|
||||
app.UseFrameGuard(new FrameGuardOptions(FrameGuardOptions.FrameGuard.SAMEORIGIN));
|
||||
app.UseFrameGuard(new FrameGuardOptions("http://amazon.com"));
|
||||
app.UseHsts(maxAge: 5000, includeSubDomains: true, preload: false);
|
||||
app.UseReferrerPolicy(ReferrerPolicy.NoReferrer);
|
||||
app.UseIENoOpen();
|
||||
app.UseNoMimeSniff();
|
||||
app.UseCrossSiteScriptingFilters();
|
||||
app.UseServerHeader("PoopyServer");
|
||||
app.UseContentSecurityPolicy(
|
||||
new ContentSecurityPolicyBuilder()
|
||||
.WithDefaultSource(CSPConstants.Self)
|
||||
.WithImageSource("http://images.mysite.com")
|
||||
.WithFontSource(CSPConstants.Self)
|
||||
.WithFrameAncestors(CSPConstants.None)
|
||||
.WithMediaSource(CSPConstants.Schemes.MediaStream)
|
||||
.BuildPolicy()
|
||||
);
|
||||
app.UseStaticFiles();
|
||||
|
||||
new ContentSecurityPolicyBuilder()
|
||||
.WithFontSource(CSPConstants.Self)
|
||||
.WithImageSource("https://example.com")
|
||||
.WithSandBox(SandboxOption.AllowForms);
|
||||
|
||||
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace HardHat.UnitTests
|
||||
Assert.Throws<ArgumentNullException>(() => new FrameGuard(null, null));
|
||||
Assert.Throws<ArgumentNullException>(() => new ReferrerPolicyMiddleware(null, null));
|
||||
Assert.Throws<ArgumentNullException>(() => new FrameGuardOptions(string.Empty));
|
||||
Assert.Throws<ArgumentNullException>(() => new ContentSecurityPolicyMiddleware(null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
src/HardHat.UnitTests/CSPBuilderTests.cs
Normal file
52
src/HardHat.UnitTests/CSPBuilderTests.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using HardHat.Builders;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace HardHat.UnitTests
|
||||
{
|
||||
public class CSPBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultBuilderTests()
|
||||
{
|
||||
//"default-src 'self' http://*.example.com; "
|
||||
var builder = ContentSecurityHeaderBuilder.Build(new ContentSecurityPolicy() {
|
||||
DefaultSrc = new HashSet<string>() { CSPConstants.Self, CSPConstants.None, "http://*.example.com" },
|
||||
ScriptSrc = new HashSet<string>() { "http://*.example.com" },
|
||||
StyleSrc = new HashSet<string>() { "http://*.example.com" },
|
||||
ImgSrc = new HashSet<string>() { "http://*.example.com" },
|
||||
ConnectSrc = new HashSet<string>() { "http://*.example.com" },
|
||||
FontSrc = new HashSet<string>() { "http://*.example.com" },
|
||||
ObjectSrc = new HashSet<string>() { "http://*.example.com" },
|
||||
MediaSrc = new HashSet<string>() { "http://*.example.com" },
|
||||
ChildSrc = new HashSet<string>() { "http://*.example.com" },
|
||||
FormAction = new HashSet<string>() { "http://*.example.com" },
|
||||
FrameAncestors = new HashSet<string>() { "http://*.example.com" },
|
||||
PluginTypes = new HashSet<string>() { "http://*.example.com" },
|
||||
Sandbox = SandboxOption.AllowPointerLock
|
||||
|
||||
});
|
||||
Assert.Equal<string>(@"default-src 'self' 'none' http://*.example.com; script-src http://*.example.com; style-src http://*.example.com; img-src http://*.example.com; connect-src http://*.example.com; font-src http://*.example.com; object-src http://*.example.com; media-src http://*.example.com; child-src http://*.example.com; form-action http://*.example.com; frame-ancestors http://*.example.com; sandbox allow-pointer-lock; plugin-types http://*.example.com;", builder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsReportHeaderSetProperly()
|
||||
{
|
||||
var result = ContentSecurityHeaderBuilder.Build(new ContentSecurityPolicy()
|
||||
{
|
||||
ReportUri = "/yo",
|
||||
OnlySendReport = false
|
||||
});
|
||||
Assert.Equal("report-uri /yo;", result);
|
||||
|
||||
var result2 = ContentSecurityHeaderBuilder.Build(new ContentSecurityPolicy()
|
||||
{
|
||||
ReportUri = "/yo",
|
||||
OnlySendReport = true
|
||||
});
|
||||
Assert.Equal("report-uri-Report-Only /yo;", result2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\HardHat\HardHat.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
97
src/HardHat/Builders/ContentSecurityHeaderBuilder.cs
Normal file
97
src/HardHat/Builders/ContentSecurityHeaderBuilder.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace HardHat.Builders
|
||||
{
|
||||
internal class ContentSecurityHeaderBuilder
|
||||
{
|
||||
public static string Build(ContentSecurityPolicy policy)
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
if (policy == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(policy));
|
||||
}
|
||||
if (policy.DefaultSrc != null && policy.DefaultSrc.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.DefaultSrc);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.DefaultSrc)}; ");
|
||||
}
|
||||
if (policy.ScriptSrc != null && policy.ScriptSrc.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.ScriptSrc);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.ScriptSrc)}; ");
|
||||
}
|
||||
if (policy.StyleSrc != null && policy.StyleSrc.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.StyleSrc);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.StyleSrc)}; ");
|
||||
}
|
||||
if (policy.ImgSrc != null && policy.ImgSrc.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.ImgSrc);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.ImgSrc)}; ");
|
||||
}
|
||||
if (policy.ConnectSrc != null && policy.ConnectSrc.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.ConnectSrc);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.ConnectSrc)}; ");
|
||||
}
|
||||
if (policy.FontSrc != null && policy.FontSrc.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.FontSrc);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.FontSrc)}; ");
|
||||
}
|
||||
if (policy.ObjectSrc != null && policy.ObjectSrc.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.ObjectSrc);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.ObjectSrc)}; ");
|
||||
}
|
||||
if (policy.MediaSrc != null && policy.MediaSrc.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.MediaSrc);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.MediaSrc)}; ");
|
||||
}
|
||||
if (policy.ChildSrc != null && policy.ChildSrc.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.ChildSrc);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.ChildSrc)}; ");
|
||||
}
|
||||
if (policy.FormAction != null && policy.FormAction.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.FormAction);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.FormAction)}; ");
|
||||
}
|
||||
if (policy.FrameAncestors != null && policy.FrameAncestors.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.FrameAncestors);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.FrameAncestors)}; ");
|
||||
}
|
||||
if (policy.Sandbox != null)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.Sandbox);
|
||||
stringBuilder.Append($" {policy.Sandbox.Value}; ");
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(policy.ReportUri))
|
||||
{
|
||||
|
||||
if (policy.OnlySendReport)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.ReportUriReportOnly);
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.ReportUri);
|
||||
}
|
||||
stringBuilder.Append($" {policy.ReportUri}; ");
|
||||
}
|
||||
if(policy.PluginTypes != null && policy.PluginTypes.Count > 0)
|
||||
{
|
||||
stringBuilder.Append(Constants.CSPDirectives.PluginTypes);
|
||||
stringBuilder.Append($" {string.Join(" ", policy.PluginTypes)}; ");
|
||||
}
|
||||
return stringBuilder.ToString().TrimEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/HardHat/CSPConstants.cs
Normal file
87
src/HardHat/CSPConstants.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace HardHat
|
||||
{
|
||||
public static class CSPConstants
|
||||
{
|
||||
internal static string NonceHyphen = "nonce-";
|
||||
internal static string sha256 = "sha256-";
|
||||
internal static string sha384 = "sha384-";
|
||||
internal static string sha512 = "sha512-";
|
||||
|
||||
/// <summary>
|
||||
/// Refers to the origin from which the protected document is being served, including the same URL scheme and port number. You must include the single quotes. Some browsers specifically exclude blob and filesystem from source directives. Sites needing to allow these content types can specify them using the Data attribute. This can be found at Sources.Scheme.*
|
||||
/// </summary>
|
||||
public const string Self = @"'self'";
|
||||
/// <summary>
|
||||
/// Allows the use of inline resources, such as inline <script> elements, javascript: URLs, inline event handlers, and inline <style> elements. You must include the single quotes.
|
||||
/// </summary>
|
||||
public const string UnsafeInline = "unsafe-inline";
|
||||
/// <summary>
|
||||
/// Allows the use of eval() and similar methods for creating code from strings. You must include the single quotes.
|
||||
/// </summary>
|
||||
public const string UnsafeEval = "unsafe-eval";
|
||||
/// <summary>
|
||||
/// Refers to the empty set; that is, no URLs match. The single quotes are required.
|
||||
/// </summary>
|
||||
public const string None = @"'none'";
|
||||
/// <summary>
|
||||
/// The strict-dynamic source expression specifies that the trust explicitly given to a script present in the markup, by accompanying it with a nonce or a hash, shall be propagated to all the scripts loaded by that root script. At the same time, any whitelist or source expressions such as 'self' or 'unsafe-inline' will be ignored.
|
||||
/// </summary>
|
||||
public const string StrictDynamic = "strict-dynamic";
|
||||
/// <summary>
|
||||
/// A whitelist for specific inline scripts using a cryptographic nonce (number used once). The server must generate a unique nonce value each time it transmits a policy. It is critical to provide an unguessable nonce, as bypassing a resource’s policy is otherwise trivial. See unsafe inline script for an example.
|
||||
/// </summary>
|
||||
/// <param name="base64Value"></param>
|
||||
/// <returns></returns>
|
||||
public static string Nonce(string base64Value) => CSPConstants.NonceHyphen + base64Value;
|
||||
/// <summary>
|
||||
/// A sha256, of inline scripts or styles. When generating the hash, don't include the <script> or <style> tags and note that capitalization and whitespace matter, including leading or trailing whitespace.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns></returns>
|
||||
public static string Sha256(string hash) => CSPConstants.sha256 + hash;
|
||||
/// <summary>
|
||||
/// A sha384 of inline scripts or styles. When generating the hash, don't include the <script> or <style> tags and note that capitalization and whitespace matter, including leading or trailing whitespace.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns></returns>
|
||||
public static string Sha384(string hash) => CSPConstants.sha384 + hash;
|
||||
/// <summary>
|
||||
/// A sha512 of inline scripts or styles. When generating the hash, don't include the <script> or <style> tags and note that capitalization and whitespace matter, including leading or trailing whitespace.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns></returns>
|
||||
public static string Sha512(string hash) => CSPConstants.sha512 + hash;
|
||||
|
||||
public static class Schemes
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows data: URIs to be used as a content source. This is insecure; an attacker can also inject arbitrary data: URIs. Use this sparingly and definitely not for scripts.
|
||||
/// </summary>
|
||||
public const string Data = "data:";
|
||||
/// <summary>
|
||||
/// Allows mediastream: URIs to be used as a content source.
|
||||
/// </summary>
|
||||
public const string MediaStream = "mediastream:";
|
||||
/// <summary>
|
||||
/// Allows blob: URIs to be used as a content source.
|
||||
/// </summary>
|
||||
public const string Blob = "blob:";
|
||||
/// <summary>
|
||||
/// Allows filesystem: URIs to be used as a content source.
|
||||
/// </summary>
|
||||
public const string filesystem = "filesystem:";
|
||||
|
||||
public const string Http = "http:";
|
||||
|
||||
public const string Https = "https:";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
{
|
||||
internal static class Constants
|
||||
{
|
||||
internal const string ContentSecurityPolicyHeader = "Content-Security-Policy";
|
||||
internal const string DnsControlHeader = "X-DNS-Prefetch-Control";
|
||||
internal const string FrameGuardHeader = "X-Frame-Options";
|
||||
internal const string DowloadOptions = "X-Download-Options";
|
||||
@@ -18,7 +19,7 @@
|
||||
internal const string Zero = "0";
|
||||
internal const string UserAgent = "User-Agent";
|
||||
internal const string ServerHeader = "Server";
|
||||
|
||||
internal const string semicolon = ";";
|
||||
internal static class Referrers
|
||||
{
|
||||
internal const string NoReferrer = "no-referrer";
|
||||
@@ -30,5 +31,24 @@
|
||||
internal const string StrictOriginWhenCrossOrigin = "strict-origin-when-cross-origin";
|
||||
internal const string UnsafeUrl = "unsafe-url";
|
||||
}
|
||||
|
||||
internal static class CSPDirectives
|
||||
{
|
||||
internal const string DefaultSrc = "default-src";
|
||||
internal const string ScriptSrc = "script-src";
|
||||
internal const string StyleSrc = "style-src";
|
||||
internal const string ImgSrc = "img-src";
|
||||
internal const string ConnectSrc = "connect-src";
|
||||
internal const string FontSrc = "font-src";
|
||||
internal const string ObjectSrc = "object-src";
|
||||
internal const string MediaSrc = "media-src";
|
||||
internal const string Sandbox = "sandbox";
|
||||
internal const string ReportUri = "report-uri";
|
||||
internal const string ReportUriReportOnly = "report-uri-Report-Only";
|
||||
internal const string ChildSrc = "child-src";
|
||||
internal const string FormAction = "form-action";
|
||||
internal const string FrameAncestors = "frame-ancestors";
|
||||
internal const string PluginTypes = "plugin-types";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
70
src/HardHat/ContentSecurityPolicy.cs
Normal file
70
src/HardHat/ContentSecurityPolicy.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace HardHat
|
||||
{
|
||||
public class ContentSecurityPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The default-src is the default policy for loading content such as JavaScript, Images, CSS, Font's, AJAX requests, Frames, HTML5 Media.
|
||||
/// </summary>
|
||||
public HashSet<string> DefaultSrc { get; set; } = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// Defines valid sources of JavaScript.
|
||||
/// </summary>
|
||||
public HashSet<string> ScriptSrc { get; set; } = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// Defines valid sources of stylesheets.
|
||||
/// </summary>
|
||||
public HashSet<string> StyleSrc { get; set; } = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// Defines valid sources of images.
|
||||
/// </summary>
|
||||
public HashSet<string> ImgSrc { get; set; } = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// Applies to XMLHttpRequest (AJAX), WebSocket or EventSource. If not allowed the browser emulates a 400 HTTP status code.
|
||||
/// </summary>
|
||||
public HashSet<string> ConnectSrc { get; set; } = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// Defines valid sources of fonts.
|
||||
/// </summary>
|
||||
public HashSet<string> FontSrc { get; set; } = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// Defines valid sources of plugins, eg <object>, <embed> or <applet>.
|
||||
/// </summary>
|
||||
public HashSet<string> ObjectSrc { get; set; } = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// Defines valid sources of audio and video, eg HTML5 <audio>, <video> elements.
|
||||
/// </summary>
|
||||
public HashSet<string> MediaSrc { get; set; } = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// Defines valid sources for web workers and nested browsing contexts loaded using elements such as <frame> and <iframe>
|
||||
/// </summary>
|
||||
public HashSet<string> ChildSrc { get; set; } = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// Defines valid sources that can be used as a HTML <form> action.
|
||||
/// </summary>
|
||||
public HashSet<string> FormAction { get; set; } = new HashSet<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Defines valid sources for embedding the resource using <frame> <iframe> <object> <embed> <applet>. Setting this directive to 'none' should be roughly equivalent to X-Frame-Options: DENY
|
||||
/// </summary>
|
||||
public HashSet<string> FrameAncestors { get; set; } = new HashSet<string>();
|
||||
/// <summary>
|
||||
/// Defines valid MIME types for plugins invoked via <object> and <embed>. To load an <applet> you must specify application/x-java-applet.
|
||||
/// </summary>
|
||||
public HashSet<string> PluginTypes { get; set; } = new HashSet<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP Content-Security-Policy (CSP) sandbox directive enables a sandbox for the requested resource similar to the <iframe> sandbox attribute. It applies restrictions to a page's actions including preventing popups, preventing the execution of plugins and scripts, and enforcing a same-origin policy.
|
||||
/// </summary>
|
||||
public BaseSandboxOption Sandbox { get; set; } = null;
|
||||
|
||||
|
||||
public string ReportUri = string.Empty;
|
||||
|
||||
public bool OnlySendReport { get; set; } = false;
|
||||
|
||||
}
|
||||
}
|
||||
206
src/HardHat/ContentSecurityPolicyBuilder.cs
Normal file
206
src/HardHat/ContentSecurityPolicyBuilder.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace HardHat
|
||||
{
|
||||
public class ContentSecurityPolicyBuilder
|
||||
{
|
||||
internal readonly ContentSecurityPolicy Policy = new ContentSecurityPolicy();
|
||||
|
||||
/// <summary>
|
||||
/// The default-src is the default policy for loading content such as JavaScript, Images, CSS, Font's, AJAX requests, Frames, HTML5 Media.
|
||||
/// </summary>
|
||||
/// <param name="includeSelf"></param>
|
||||
/// <param name=""></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithDefaultSource(params string[] sources)
|
||||
{
|
||||
if(sources == null || sources.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(sources));
|
||||
}
|
||||
Policy.DefaultSrc.UnionWith(sources);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Defines valid sources of JavaScript.
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithScriptSource(params string[] sources)
|
||||
{
|
||||
if (sources == null || sources.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(sources));
|
||||
}
|
||||
Policy.ScriptSrc.UnionWith(sources);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Defines valid sources of stylesheets.
|
||||
/// </summary>
|
||||
/// <param name="souces"></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithStyleSource(params string[] sources)
|
||||
{
|
||||
if (sources == null || sources.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(sources));
|
||||
}
|
||||
Policy.StyleSrc.UnionWith(sources);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Defines valid sources of images.
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithImageSource(params string[] sources)
|
||||
{
|
||||
if (sources == null || sources.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(sources));
|
||||
}
|
||||
Policy.ImgSrc.UnionWith(sources);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Applies to XMLHttpRequest (AJAX), WebSocket or EventSource. If not allowed the browser emulates a 400 HTTP status code.
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithConnectSource(params string[] sources)
|
||||
{
|
||||
if (sources == null || sources.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(sources));
|
||||
}
|
||||
Policy.ConnectSrc.UnionWith(sources);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Defines valid sources of fonts.
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithFontSource(params string[] sources)
|
||||
{
|
||||
if (sources == null || sources.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(sources));
|
||||
}
|
||||
Policy.FontSrc.UnionWith(sources);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Defines valid sources of plugins, eg <object>, <embed> or <applet>.
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithObjectSource(params string[] sources)
|
||||
{
|
||||
if (sources == null || sources.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(sources));
|
||||
}
|
||||
Policy.ObjectSrc.UnionWith(sources);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Defines valid sources of audio and video, eg HTML5 <audio>, <video> elements.
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithMediaSource(params string[] sources)
|
||||
{
|
||||
if (sources == null || sources.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(sources));
|
||||
}
|
||||
Policy.MediaSrc.UnionWith(sources);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Instructs the browser to POST reports of policy failures to this URI. You can also append -Report-Only to the HTTP header name to instruct the browser to only send reports (does not block anything).
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithReportUri(string uri, bool OnlySendReport = false)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(uri))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uri));
|
||||
}
|
||||
Policy.ReportUri = uri;
|
||||
Policy.OnlySendReport = OnlySendReport;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Defines valid sources for web workers and nested browsing contexts loaded using elements such as <frame> and <iframe>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithChildSource(params string[] sources)
|
||||
{
|
||||
if(sources == null || sources.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(sources));
|
||||
}
|
||||
Policy.ChildSrc.UnionWith(sources);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Defines valid sources that can be used as a HTML <form> action.
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithFormAction(params string[] sources)
|
||||
{
|
||||
if (sources == null || sources.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(sources));
|
||||
}
|
||||
Policy.FormAction.UnionWith(sources);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Defines valid sources for embedding the resource using <frame> <iframe> <object> <embed> <applet>. Setting this directive to 'none' should be roughly equivalent to X-Frame-Options: DENY
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithFrameAncestors(params string[] sources)
|
||||
{
|
||||
if (sources == null || sources.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(sources));
|
||||
}
|
||||
Policy.FrameAncestors.UnionWith(sources);
|
||||
return this;
|
||||
}
|
||||
|
||||
// plugin-types plugin-types application/x-shockwave-flash
|
||||
|
||||
/// <summary>
|
||||
/// Defines valid MIME types for plugins invoked via <object> and <embed>. To load an <applet> you must specify application/x-java-applet.
|
||||
/// </summary>
|
||||
/// <param name="mimeTypes"></param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithPluginTypes(params string[] mimeTypes)
|
||||
{
|
||||
if (mimeTypes == null || mimeTypes.Length < 1)
|
||||
{
|
||||
throw new ArgumentException(nameof(mimeTypes));
|
||||
}
|
||||
Policy.PluginTypes.UnionWith(mimeTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentSecurityPolicyBuilder WithSandBox(BaseSandboxOption sandboxOption)
|
||||
{
|
||||
Policy.Sandbox = sandboxOption ?? throw new ArgumentNullException(nameof(sandboxOption));
|
||||
return this;
|
||||
}
|
||||
public ContentSecurityPolicy BuildPolicy() => Policy;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,14 @@ namespace Microsoft.AspNetCore.Builder
|
||||
/// <returns></returns>
|
||||
public static IApplicationBuilder UseCrossSiteScriptingFilters(this IApplicationBuilder app, bool addOldIE = false) => app.UseMiddleware<XSSProtection>(addOldIE);
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP Content-Security-Policy response header allows web site administrators to control resources the user agent is allowed to load for a given page. With a few exceptions, policies mostly involve specifying server origins and script endpoints. This helps guard against cross-site scripting attacks
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="policy"></param>
|
||||
/// <returns></returns>
|
||||
public static IApplicationBuilder UseContentSecurityPolicy(this IApplicationBuilder app, ContentSecurityPolicy policy) => app.UseMiddleware<ContentSecurityPolicyMiddleware>(policy);
|
||||
|
||||
/// <summary>
|
||||
/// change or remove the server header.
|
||||
/// </summary>
|
||||
|
||||
30
src/HardHat/Middlewares/ContentSecurityPolicyMiddleware.cs
Normal file
30
src/HardHat/Middlewares/ContentSecurityPolicyMiddleware.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using HardHat.Builders;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HardHat.Middlewares
|
||||
{
|
||||
public class ContentSecurityPolicyMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly string _policy;
|
||||
public ContentSecurityPolicyMiddleware(RequestDelegate next, ContentSecurityPolicy policy)
|
||||
{
|
||||
if(policy == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(policy));
|
||||
}
|
||||
_policy = ContentSecurityHeaderBuilder.Build(policy);
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public Task Invoke(HttpContext context)
|
||||
{
|
||||
context.Response.Headers[Constants.ContentSecurityPolicyHeader] = _policy;
|
||||
return _next?.Invoke(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace HardHat
|
||||
namespace HardHat.Middlewares
|
||||
{
|
||||
public class XSSProtection
|
||||
{
|
||||
116
src/HardHat/Models/SandboxOptions.cs
Normal file
116
src/HardHat/Models/SandboxOptions.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace HardHat
|
||||
{
|
||||
public abstract class BaseSandboxOption
|
||||
{
|
||||
internal BaseSandboxOption() { }
|
||||
public abstract string Value { get; }
|
||||
}
|
||||
|
||||
public class AllowForms : BaseSandboxOption
|
||||
{
|
||||
internal AllowForms() { }
|
||||
public override string Value => "allow-forms";
|
||||
}
|
||||
|
||||
public class AllowModals : BaseSandboxOption
|
||||
{
|
||||
internal AllowModals() { }
|
||||
public override string Value => "allow-modals";
|
||||
}
|
||||
|
||||
public class AllowOrientationLock : BaseSandboxOption
|
||||
{
|
||||
internal AllowOrientationLock() { }
|
||||
public override string Value => "allow-orientation-lock";
|
||||
}
|
||||
|
||||
public class AllowPointerLock : BaseSandboxOption
|
||||
{
|
||||
internal AllowPointerLock() { }
|
||||
public override string Value => "allow-pointer-lock";
|
||||
}
|
||||
|
||||
public class AllowPopups : BaseSandboxOption
|
||||
{
|
||||
internal AllowPopups() { }
|
||||
public override string Value => "allow-popups";
|
||||
}
|
||||
public class AllowPopupsToEscapeSandbox : BaseSandboxOption
|
||||
{
|
||||
internal AllowPopupsToEscapeSandbox() { }
|
||||
public override string Value => "allow-popups-to-escape-sandbox";
|
||||
}
|
||||
|
||||
public class AllowPresentation : BaseSandboxOption
|
||||
{
|
||||
internal AllowPresentation() { }
|
||||
public override string Value => "allow-presentation";
|
||||
}
|
||||
|
||||
public class AllowSameOrigin : BaseSandboxOption
|
||||
{
|
||||
internal AllowSameOrigin() { }
|
||||
public override string Value => "allow-same-origin";
|
||||
}
|
||||
|
||||
public class AllowScripts : BaseSandboxOption
|
||||
{
|
||||
internal AllowScripts() { }
|
||||
public override string Value => "allow-scripts";
|
||||
}
|
||||
|
||||
public class AllowTopNaviation : BaseSandboxOption
|
||||
{
|
||||
internal AllowTopNaviation() { }
|
||||
public override string Value => "allow-top-navigation";
|
||||
}
|
||||
|
||||
public static class SandboxOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows the embedded browsing context to submit forms. If this keyword is not used, this operation is not allowed.
|
||||
/// </summary>
|
||||
public static AllowForms AllowForms = new AllowForms();
|
||||
/// <summary>
|
||||
/// Allows the embedded browsing context to open modal windows.
|
||||
/// </summary>
|
||||
public static AllowModals AllowModals = new AllowModals();
|
||||
/// <summary>
|
||||
/// Allows the embedded browsing context to disable the ability to lock the screen orientation.
|
||||
/// </summary>
|
||||
public static AllowOrientationLock AllowOrientationLock = new AllowOrientationLock();
|
||||
/// <summary>
|
||||
/// Allows the embedded browsing context to use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API">Pointer Lock API</a>
|
||||
/// </summary>
|
||||
public static AllowPointerLock AllowPointerLock = new AllowPointerLock();
|
||||
|
||||
/// <summary>
|
||||
/// Allows popups (like from window.open, target="_blank", showModalDialog). If this keyword is not used, that functionality will silently fail.
|
||||
/// </summary>
|
||||
public static AllowPopups AllowPopups = new AllowPopups();
|
||||
/// <summary>
|
||||
/// Allows a sandboxed document to open new windows without forcing the sandboxing flags upon them. This will allow, for example, a third-party advertisement to be safely sandboxed without forcing the same restrictions upon a landing page.
|
||||
/// </summary>
|
||||
public static AllowPopupsToEscapeSandbox AllowPopupsToEscapeSandbox = new AllowPopupsToEscapeSandbox();
|
||||
/// <summary>
|
||||
/// Allows embedders to have control over whether an iframe can start a presentation session.
|
||||
/// </summary>
|
||||
public static AllowPresentation AllowPresentation = new AllowPresentation();
|
||||
/// <summary>
|
||||
/// Allows the content to be treated as being from its normal origin. If this keyword is not used, the embedded content is treated as being from a unique origin.
|
||||
/// </summary>
|
||||
public static AllowSameOrigin AllowSameOrigin = new AllowSameOrigin();
|
||||
/// <summary>
|
||||
/// Allows the embedded browsing context to run scripts (but not create pop-up windows). If this keyword is not used, this operation is not allowed.
|
||||
/// </summary>
|
||||
public static AllowScripts AllowScripts = new AllowScripts();
|
||||
/// <summary>
|
||||
/// Allows the embedded browsing context to navigate (load) content to the top-level browsing context. If this keyword is not used, this operation is not allowed.
|
||||
/// </summary>
|
||||
public static AllowTopNaviation AllowTopNaviation = new AllowTopNaviation();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user