So you’ve been tasked with applying CSP headers, and now you’ve landed here. Welcome! Hopefully, I can alleviate many of the problems you might run into. Perhaps you don’t know what CSP headers even are.
Recently, we at BizStream have seen an uptick in the awareness and need for Content Security Policy headers on our clients’ sites. Here, I will outline what I’ve done to solve the issue of applying CSP headers to a pre-existing site.
From MDN, Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft to site defacement, to malware distribution.
In other words, it’s a way to whitelist everything that your site uses as a safe resource, such that it will reject anything else. While its purpose is to protect your site from malicious attacks, it is only your first line of defense and is not intended to be a complete solution for preventing attacks. For a more in-depth explanation of Content Security Policy, you can check out Mike West and Joe Medley’s article, An Introduction to Content Security Policy.
From MDN, Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft to site defacement, to malware distribution.
In other words, it’s a way to whitelist everything that your site uses as a safe resource, such that it will reject anything else. While its purpose is to protect your site from malicious attacks, it is only your first line of defense and is not intended to be a complete solution for preventing attacks. For a more in-depth explanation of Content Security Policy, you can check out Mike West and Joe Medley’s article, An Introduction to Content Security Policy.
Policy directives describe rules for a particular resource or policy area. For example, the following policy directive will allow content from the same domain and trusted.com, including all subdomains.
script-src 'self' trusted.com *.trusted.com
I won’t go over every available directive (there are a lot of them!). The best practice would be to set every available directive, if possible. The important ones to note are script-src and style-src. With the CSP Level 3 specification, we find a saving grace in the new strict-dynamic directive. This allows us to mark which inline scripts and styles are safe to run. Consequently, this also automatically marks scripts and styles inserted by the marked scripts as safe to run. For example, Google Tag Manager will insert analytics scripts when it initializes. If you’re wondering about what a CSP level is, it’s just the specification version. Level 3 is the latest iteration as of writing this blog post. If you’re interested in reading up, you can go directly to the source from W3.
When working with Kentico, there are quite a few hoops to jump through to ensure your site is secure. Unfortunately, to fully secure the Kentico Mother, we would need to edit the source files of Kentico. This is a big red flag for pain down the line when upgrading to the next Kentico version. Therefore, we are currently unable to secure the Kentico Mother with CSP and will need to create a bypass instead.
Amongst finding all sources for our resources to add to our CSP headers, we have a small laundry list of things we need to add for our site to function with the CSP restrictions. My example was built in .NET Framework 4.8; the same concept is possible but built differently in .NET Core. However, The concepts should still carry over.
Here’s an outline of the things we need to do in order to secure our site:
First, we can create the HtmlOutputFilter base class that our other filters will inherit in order to share the ability to parse and edit the HtmlOutput of our controllers.
// Filters/HtmlOutputFilter.cs
public class HtmlOutputFilter : MemoryStream
{
private readonly Stream stream;
public HtmlOutputFilter(Stream stream)
{
this.stream = stream;
}
public override void Write(byte[] buffer, int offset, int count)
{
var html = Encoding.UTF8.GetString(buffer);
html = TransformHtmlOutput(html);
buffer = Encoding.UTF8.GetBytes(html);
stream.Write(buffer, offset, buffer.Length);
}
public virtual string TransformHtmlOutput(string htmlString)
{
throw new Exception($"{nameof(HtmlOutputFilter)} was not overridden or base.${nameof(TransformHtmlOutput)} was called.");
}
}
Next, we can build out our two filters to add nonces and remove the inline onsubmit event handlers.
On the topic of nonces, I should probably explain what these are a little. Nonces are a one-time use value. For CSP headers, we supply a nonce to the headers themselves and also add a nonce attribute to each inline script and style tag on our page. This way, the browser knows which inline elements to trust. If we were to statically define our nonces, they would be meaningless. An attacker could simply look at the nonce value, add it to their scripts, and inject their malicious scripts on the next request. What we are doing is using NWebsec to dynamically generate a nonce per request. That way, an attacker does not have a way to know the nonce beforehand.
// Filters/AddNonceToScriptsAndStylesFilter.cs
public class AddNonceToScriptsAndStylesFilter : HtmlOutputFilter
{
public AddNonceToScriptsAndStylesFilter(Stream stream)
: base(stream) { }
public override string TransformHtmlOutput(string htmlString)
{
var nonceMatch = Regex.Match(htmlString, "nonce=\"(.*?)\"");
// Regex gets all script tags that do not have a nonce attribute.
var stringWithNonces = Regex.Replace(
htmlString,
"(
body>
Now you can run your application! You may find that you missed a couple of spots, or your directives may be a bit too restrictive. This is a lot of trial and error. Each site is unique, so I can only help you so much. You may even run into issues I haven’t experienced yet. To analyze the strength of your headers, you can use CSP Scanner. Overall, CSP headers can be quite a beast to conquer, especially if you’re retrofitting the headers. Hopefully, this post helps make your life easier when trying to implement CSP into your application.
We love to make cool things with cool people. Have a project you’d like to collaborate on? Let’s chat!
Stay up to date on what BizStream is doing and keep in the loop on the latest in marketing & technology.