Skip to content

Instantly share code, notes, and snippets.

@vanillajonathan
Created November 22, 2017 15:15
Show Gist options
  • Save vanillajonathan/e3dd3a796c106f10b9ea7e72c24f3a5a to your computer and use it in GitHub Desktop.
Save vanillajonathan/e3dd3a796c106f10b9ea7e72c24f3a5a to your computer and use it in GitHub Desktop.
Builds a Content Security Policy.
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