Created
November 22, 2017 15:15
-
-
Save vanillajonathan/e3dd3a796c106f10b9ea7e72c24f3a5a to your computer and use it in GitHub Desktop.
Builds a Content Security Policy.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics.Contracts; | |
using System.Text; | |
using Microsoft.AspNetCore.Mvc.Filters; | |
namespace WebApplication | |
{ | |
/// <summary> | |
/// A filter that builds a Content Security Policy. | |
/// Mitigates the risk of XSS vulnerabilities. | |
/// </summary> | |
/// <remarks>https://www.w3.org/TR/CSP/</remarks> | |
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] | |
public class ContentSecurityPolicyAttribute : Attribute, IActionFilter | |
{ | |
public const string Header = "Content-Security-Policy"; | |
public const string HeaderReportOnly = "Content-Security-Policy-Report-Only"; | |
public const string None = "'none'"; | |
public const string Self = "'self'"; | |
/// <summary> | |
/// Restricts the URLs which can be used in a <c>Document</c>'s <c>base</c> element. | |
/// </summary> | |
public string BaseUri { get; set; } | |
/// <summary> | |
/// Prevents loading any assets using HTTP when the page is loaded using HTTPS. | |
/// </summary> | |
public bool BlockAllMixedContent { get; set; } | |
/// <summary> | |
/// Governs the creation of nested browsing contexts (e.g. <c>iframe</c> and <c>frame</c> navigations) and Worker execution contexts. | |
/// </summary> | |
public string ChildSrc { get; set; } | |
/// <summary> | |
/// Restricts the URLs which can be loaded using script interfaces. | |
/// </summary> | |
public string ConnectSrc { get; set; } | |
/// <summary> | |
/// Serves as a fallback for the other fetch directives. | |
/// </summary> | |
public string DefaultSrc { get; set; } | |
/// <summary> | |
/// Ensures that a resource will disown its opener when navigated to. | |
/// </summary> | |
public bool DisownOpener { get; set; } | |
/// <summary> | |
/// Restricts the URLs from which font resources may be loaded. | |
/// </summary> | |
public string FontSrc { get; set; } | |
/// <summary> | |
/// Restricts the URLs which can be used as the target of a form submissions from a given context. | |
/// </summary> | |
public string FormAction { get; set; } | |
/// <summary> | |
/// Restricts the URLs which can embed the resource using <c>frame</c>, <c>iframe</c>, <c>object</c>, <c>embed</c>, or <c>applet</c> element. | |
/// Resources can use this directive to avoid many UI Redressing attacks, by avoiding the risk of being | |
/// embedded into potentially hostile contexts. | |
/// </summary> | |
public string FrameAncestors { get; set; } | |
/// <summary> | |
/// Restricts the URLs which may be loaded into nested browsing contexts. | |
/// </summary> | |
public string FrameSrc { get; set; } | |
/// <summary> | |
/// Restricts the URLs from which image resources may be loaded. | |
/// </summary> | |
public string ImgSrc { get; set; } | |
/// <summary> | |
/// Restricts the URLs from which application manifests may be loaded. | |
/// </summary> | |
public string ManifestSrc { get; set; } | |
/// <summary> | |
/// Restricts the URLs from which video, audio, and associated text track resources may be loaded. | |
/// </summary> | |
public string MediaSrc { get; set; } | |
/// <summary> | |
/// Restricts the URLs from which plugin content may be loaded. | |
/// </summary> | |
public string ObjectSrc { get; set; } | |
/// <summary> | |
/// Restricts the set of plugins that can be embedded into a document by limiting the types of resources which can be loaded. | |
/// </summary> | |
/// <remarks> | |
/// Value should be a MIME type. | |
/// </remarks> | |
public string PluginTypes { get; set; } | |
/// <summary> | |
/// Specifies an HTML sandbox policy which the user agent will apply to a resource, just as though it had been included in an <c>iframe</c> with a <c>sandbox</c> property. | |
/// </summary> | |
/// <remarks> | |
/// Valid values include: <c>allow-forms</c>, <c>allow-modals</c>, <c>allow-orientation-lock</c>, | |
/// <c>allow-pointer-lock</c>, <c>allow-popups</c>, <c>allow-popups-to-escape-sandbox</c>, | |
/// <c>allow-presentation</c>, <c>allow-same-origin</c>, <c>allow-scripts</c>, | |
/// <c>allow-top-navigation</c>, and <c>allow-top-navigation-by-user-activation</c>. | |
/// </remarks> | |
public string Sandbox { get; set; } | |
/// <summary> | |
/// Restricts the locations from which scripts may be executed. | |
/// </summary> | |
/// <remarks> | |
/// Valid tokens include <c>'unsafe-inline'</c> and <c>'unsafe-eval'</c>. | |
/// </remarks> | |
public string ScriptSrc { get; set; } | |
/// <summary> | |
/// Restricts the locations from which style may be applied to a <c>Document</c>. | |
/// </summary> | |
public string StyleSrc { get; set; } | |
/// <summary> | |
/// Defines a reporting group to which violation reports ought to be sent. | |
/// </summary> | |
public string ReportTo { get; set; } | |
/// <summary> | |
/// Defines a set of endpoints to which violation reports will be sent when particular behaviors are prevented. | |
/// </summary> | |
public string ReportUri { get; set; } | |
/// <summary> | |
/// Gives developers the ability to assert to the browser that every resource of a given type ought to be integrity checked. | |
/// If a resource of that type is loaded without integrity metadata, it will be rejected without triggering a network request. | |
/// </summary> | |
/// <remarks> | |
/// Valid values in include <c>script</c> and <c>style</c>. | |
/// </remarks> | |
public string RequireSriFor { get; set; } | |
/// <summary> | |
/// Instructs user agents to treat all of a site's insecure URLs as though they have been replaced with secure URLs. | |
/// </summary> | |
/// <remarks> | |
/// Intended for web sites with large numbers of insecure legacy URLs that need to be rewritten. | |
/// </remarks> | |
public bool UpgradeInsecureRequests { get; set; } | |
/// <summary> | |
/// Restricts the URLs which may be loaded as a <c>Worker</c>, <c>SharedWorker</c>, or <c>ServiceWorker</c>. | |
/// </summary> | |
public string WorkerSrc { get; set; } | |
/// <summary> | |
/// Builds a policy string. | |
/// </summary> | |
/// <returns>A policy string or an empty string.</returns> | |
[Pure] | |
public string GetPolicyString() | |
{ | |
var policy = new StringBuilder(); | |
if (DefaultSrc != null) | |
{ | |
policy.Append($"default-src {DefaultSrc}; "); | |
} | |
if (ChildSrc != null) | |
{ | |
policy.Append($"child-src {ChildSrc}; "); | |
} | |
if (ConnectSrc != null) | |
{ | |
policy.Append($"connect-src {ConnectSrc}; "); | |
} | |
if (FontSrc != null) | |
{ | |
policy.Append($"font-src {FontSrc}; "); | |
} | |
if (FrameSrc != null) | |
{ | |
policy.Append($"frame-src {FrameSrc}; "); | |
} | |
if (ImgSrc != null) | |
{ | |
policy.Append($"img-src {ImgSrc}; "); | |
} | |
if (ManifestSrc != null) | |
{ | |
policy.Append($"manifest-src {ManifestSrc}; "); | |
} | |
if (MediaSrc != null) | |
{ | |
policy.Append($"media-src {MediaSrc}; "); | |
} | |
if (ObjectSrc != null) | |
{ | |
policy.Append($"object-src {ObjectSrc}; "); | |
} | |
if (ScriptSrc != null) | |
{ | |
policy.Append($"script-src {ScriptSrc}; "); | |
} | |
if (StyleSrc != null) | |
{ | |
policy.Append($"style-src {StyleSrc}; "); | |
} | |
if (WorkerSrc != null) | |
{ | |
policy.Append($"worker-src {WorkerSrc}; "); | |
} | |
if (FormAction != null) | |
{ | |
policy.Append($"form-action '{FormAction}; "); | |
} | |
if (FrameAncestors != null) | |
{ | |
policy.Append($"frame-ancestors '{FrameAncestors}; "); | |
} | |
if (BlockAllMixedContent) | |
{ | |
policy.Append("block-all-mixed-content; "); | |
} | |
if (DisownOpener) | |
{ | |
policy.Append("disown-opener; "); | |
} | |
if (BaseUri != null) | |
{ | |
policy.Append($"base-uri {BaseUri}; "); | |
} | |
if (PluginTypes != null) | |
{ | |
policy.Append($"plugin-types {PluginTypes}; "); | |
} | |
if (ReportUri != null) | |
{ | |
policy.Append($"report-uri {ReportUri}; "); | |
} | |
if (ReportTo != null) | |
{ | |
policy.Append($"report-to {ReportTo}; "); | |
} | |
if (RequireSriFor != null) | |
{ | |
policy.Append($"require-sri-for {RequireSriFor}; "); | |
} | |
if (Sandbox != null) | |
{ | |
policy.Append($"sandbox {Sandbox}; "); | |
} | |
if (UpgradeInsecureRequests) | |
{ | |
policy.Append("upgrade-insecure-requests; "); | |
} | |
return policy.ToString(); | |
} | |
public void OnActionExecuted(ActionExecutedContext context) | |
{ | |
} | |
public void OnActionExecuting(ActionExecutingContext context) | |
{ | |
var policy = GetPolicyString(); | |
if (policy == "") | |
{ | |
return; | |
} | |
context.HttpContext.Response.Headers.Add(Header, policy); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment