.NET Content Security
Policy Guide: What It Is
and How to Enable It

stackhawk

StackHawk|February 18, 2022

The objective of this article is to assist in the concept of content security. We are going to learn .NET content security policy.

With every passing day, threats get more abundant and complex. And to mitigate the impact of these threats, software companies are constantly incorporating more sophisticated and robust solutions. 

As developers, we are responsible for ensuring that we take advantage of these solutions, adapt our software to comply with the policies, and implement the necessary measures.

One such measure is known as content security policy, or CSP. 

The objective of this article is to assist in demystifying the concept of content security.

In this article, we will

  • briefly define what CSP is,

  • illustrate how to enable CSP in our platform,

  • explore some issues you might encounter in the process, and

  • show how to address them.

Note: This article is aimed at .NET developers. Please check this article if you don't yet have enough experience in this development stack.

With that out of the way, let's jump right in.

Automated API security testing in CICD

What Is Content Security Policy?

In a nutshell, CSP is a collection of policies or directives that a browser enforces on a webpage when it requests them. 

During the process of loading a page, the browser has to request and render a significant amount of content and code to display the page.

This process is standard and—given that all modern websites are complex and composed of lots of HTML, CSS, JavaScript, and other resources like images and files that the code references—entirely expected.

This referenced code, in particular, could be from the same origin (requested domain) or another origin; browsers don't distinguish. 

Once this code is executed, the browser has to process and execute these resources without any malicious code or access data not belonging to the website in question.

In order to provide security during this process, the server provides a CSP through the response header to ensure that the browser executes only valid resources. 

This security layer helps mitigate attackers from taking advantage of vulnerabilities like cross-site scripting and injection attacks by providing an allowlist of trusted resources.

CSP Example

Let's see what a content security policy header would look like:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self';

Notice that each section in the header is relevant to a specific kind of resource. 

Further, the default "self" directive states that the browser must execute only resources from the same origin. 

Note that you can specify domains from which you want to retrieve resources by putting their URLs next to "self." 

Finally, you can permit all domains by setting "*" (don't do this unless you absolutely have to).

Enabling CSP

Now that we know what CSP is and have seen how it looks, let's implement it into our code.

To implement a simple CSP policy in ASP.NET core, we just need to add the following code to the Configure() method in the Startup.cs file before the UseEndpoints method.

app.Use(async (context, next) => {
    context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self'");

    await next();
});

This code essentially creates a middleware that adds the CSP header to all responses coming from our server.

Now you can check that all response headers contain the CSP configuration mentioned above if they didn't before. 

The browser will enforce any changes, most likely breaking your page and displaying many alerts.

Implementing appropriate content security policies requires a significant number of modifications and proper testing, which will take some time. So, for now, let's address the immediate errors while still having a functional site. 

To do this, we'll make use of the Content-Security-Policy-Report-Only directive. Just change the code to the following:

app.Use(async (context, next) => {
    context.Response.Headers.Add("Content-Security-Policy-Report-Only", "default-src 'self'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self'");

    await next();
});

Using "report-only" makes the browser no longer enforce the directives but displays the corresponding violation alerts. 

This behavior is helpful for development environments where the platform's security is not essential but the developer needs to be aware of any violations to address them adequately.

Dealing With CSP Errors

We know that seeing all these alerts in the browser developer console can be intimidating. But don't worry. Addressing these errors is quite simple once you understand what the browser tells you.

The developer console report will guide you to improve the policy directive and make the necessary updates. 

Our first step should be to confirm that the resources the browser is reporting are, in fact, legitimate and necessary for the proper functioning of the application. 

For example, we can see that our application is requesting the following:

  • https://code.jquery.com/jquery-3.5.1.min.js

  • https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js

  • https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.31/moment-timezone-with-data.js

  • https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js

  • https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js

  • https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js

  • https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css

  • https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css

  • https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff2

  • https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff

  • https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.ttf

  • https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff2

  • https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff

  • https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.ttf

  • https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff2

  • https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff

  • https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.ttf

All these are, of course, valid resources that come from trusted origins.

Once you have the list of resources at hand, you can add them to the allowlist of each respective source as follows:

app.Use(async (context, next) => {
    context.Response.Headers.Add("Content-Security-Policy-Report-Only", "default-src 'self'; script-src 'self' https://code.jquery.com/jquery-3.5.1.min.js https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.31/moment-timezone-with-data.js https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js; style-src 'self' https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css; font-src 'self' https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff2 https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.ttf https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff2 https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.ttf https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff2 https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.ttf; img-src 'self'; frame-src 'self'");

    await next();
});

By doing this, most of the alerts will go away.

Furthermore, if you want to enable all resources from a specific domain, you can do so by specifying the domain in the directive.

app.Use(async (context, next) => {
    context.Response.Headers.Add("Content-Security-Policy-Report-Only", "default-src 'self'; script-src 'self' https://code.jquery.com https://cdnjs.cloudflare.com https://stackpath.bootstrapcdn.com https://cdn.jsdelivr.net; style-src 'self' https://stackpath.bootstrapcdn.com https://cdnjs.cloudflare.com; font-src 'self' https://cdnjs.cloudflare.com; img-src 'self'; frame-src 'self'");

    await next();
});

In-line Violations

Now, what about the in-line code we have in our HTML? 

Well, there are a few things we can do to address these violations.

The first (recommended) approach is to move all in-line code and styles to a separate file and reference it. 

This way, you can ensure the security of your application and keep it consistent.

However, if this is not possible for your case, you can move the code to a <script/> or <style/> tag and use an SHA hash key to tell the browser that the code in this tag is allowed. 

You can generate the hash key by hashing the code inside the tag with a SHA256 algorithm. Conveniently, the browser already calculates this hash in the alert itself, so you can easily add it to the directive.

app.Use(async (context, next) => {
    context.Response.Headers.Add("Content-Security-Policy-Report-Only", "default-src 'self'; script-src 'self' https://code.jquery.com https://cdnjs.cloudflare.com https://stackpath.bootstrapcdn.com https://cdn.jsdelivr.net; style-src 'self' https://stackpath.bootstrapcdn.com https://cdnjs.cloudflare.com; font-src 'self' https://cdnjs.cloudflare.com; img-src 'self'; frame-src 'self'");

    await next();
});

Additionally, if you don't want to use the SHA hash, you can use what is known as a "nonce" tag attribute and add it to the corresponding tag. 

This solution will essentially serve the same purpose but must be updated with each request with some server-side code.

Finally, remember to update the directive and remove the "report-only" in production once all the changes are done.

.NET Content Security Policy Guide: What It Is and How to Enable It image

Ensuring Content Security

Building a robust and secure web platform requires expansive knowledge of development, infrastructure, and security fundamentals. 

Securing our applications can be a very complicated and long journey. It's easy to end up lost in a rabbit hole of articles and forums. Nevertheless, we must address security issues and exploits as much as possible. 

Additionally, ensuring the resilience of our platforms and the security of our users' information is a critical issue that gets more and more complex over time.

.NET is a robust and straightforward platform for building powerful and flexible web applications. Moreover, it makes the work of securing against CSP exploits very clear; there's no need to fiddle with complicated configurations or risky settings.

Furthermore, if you wish to make sure your .NET application is secure or you want to learn how to implement similar solutions in other technologies, you can find all you need in our blog resources.

This post was written by Juan Reyes. Juan is an engineer by profession and a dreamer by heart who crossed the seas to reach Japan following the promise of opportunity and challenge. While trying to find himself and build a meaningful life in the east, Juan borrows wisdom from his experiences as an entrepreneur, artist, hustler, father figure, husband, and friend to start writing about passion, meaning, self-development, leadership, relationships, and mental health. His many years of struggle and self-discovery have inspired him and drive to embark on a journey for wisdom.


StackHawk  |  February 18, 2022