Stepping up the security of ASP.NET Core web apps with security headers

Now, more than ever before, employing security best practices from the start is an absolute must when developing modern web applications.

The threat landscape has evolved continuously over the last number of years, however, so have the protection mechanisms which we as software developers can avail of to guard our applications against attackers.

In this article, I am going to cover the topic of HTTP ‘security headers’. I will explain what they are and how they can be used to greatly enhance the security of a web application. I will also demonstrate how to add security headers to an ASP.NET Core web app.

The basics

In simple terms, security headers are nothing more than straightforward HTTP headers. However, the headers which we will be looking at have a special meaning to the browser.

By default, browsers do not enable the majority of the security mechanisms we are going to cover in this article unless they are specifically told to do so.

From our web applications, we can return specific HTTP headers (with specific values) to the browser in order to tell the browser how we want it to behave and what restrictions to put in place.

On many occasions, the security measures that need to be put in place are specific to each individual web application. This is one of the main reasons why browsers typically do not apply too many restrictions initially, in case this prevents an application from functioning correctly.

In the following section, I will explain what the most important security headers are and what they are intended for.

Security headers

There are a set of key headers available which you should consider adding to every web application you develop to make it as secure as possible.

If you are using ASP.NET Core I recommend installing the NWebsec NuGet package. This is what I will be using to demonstrate adding security headers.

All of the code in the following sub-sections should be added to the Configure method your ASP.NET Core Startup class.

Content Type Options

Configuring Content Type Options with the ‘nosniff’ option disables MIME-type sniffing to prevent attacks where files are missing metadata.

The header can be configured with NWebsec as follows.

app.UseXContentTypeOptions();

This results in the header shown below.

X-Content-Type-Options: nosniff

When this security header has been specified the browser will no longer try to guess the MIME type of a resource whenever a MIME type has not been specified.

Frame Options

By configuring Frame Options appropriately, we can prevent our app from being displayed as an iframe on other websites to prevent click-jacking attacks. This header is primarily for older browsers that don’t support CSPs which we’ll cover later on.

Frame Options can be configured as follows using Nwebsec.

app.UseXfo(options => options.Deny());

This results in the header shown below.

X-Frame-Options: Deny

When this security header has been specified with the ‘Deny’ option the browser will not allow your web application to be displayed as an iframe, even as part of the same app. The ‘SameOrigin’ option can be used instead if required.

Cross Site Scripting Protection

Configuring XSS (Cross Site Scripting) Protection enables the detection of XSS attacks. This header is primarily for older browsers, as the majority of modern browsers have XSS protection enabled by default.

XSS Protection can be configured as follows using Nwebsec.

app.UseXXssProtection(options => options.EnabledWithBlockMode());

This results in the header shown below.

X-XSS-Protection: 1; mode=block

When this security header has been specified with ‘block’ mode enabled the browser will automatically detect common XSS attacks and stop them in their tracks. Most browsers will display a pop-up message to inform the user that an XSS attack has been intercepted.

Referrer Policy

A Referrer Policy can be configured to exclude the ‘Referrer’ header, which can improve security in cases where the URL of the previous web page contains sensitive data.

A Referrer Policy can be configured as follows using Nwebsec.

app.UseReferrerPolicy(opts => opts.NoReferrer());

This results in the header shown below.

Referrer-Policy: no-referrer

When this security header has been specified with the ‘no-referrer’ option the browser will omit the Referrer header entirely for maximum security.

Permissions Policy

Permissions Policy (previously known as Feature Policy) is an experimental header that can be used to restrict access to browser features that are not needed by your web application.

Although experimental, the header is already supported by some major browsers, including Chrome and Edge.

NWebsec doesn’t provide a way to set a Permissions Policy directly yet, most likely due to the fact that it is experimental. However, we can craft the header manually in ASP.NET Core, as follows.

app.Use(async (context, next) =>
{
    context.Response.Headers.Add("Permissions-Policy", "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()");
    await next();
});

This results in the header shown below.

Permissions-Policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()

You can view the full list of available permissions on the Mozilla website.

Content Security Policy

Perhaps one of the most powerful security headers, and certainly the most in-depth, is the Content Security Policy header, or CSP for short.

A CSP header allows you to configure at a very granular level what content you want to allow your web app to load and precisely which sources you want to load content from.

A CSP header can be configured as follows using Nwebsec.

app.UseCsp(options => options
    .BlockAllMixedContent()
    .DefaultSources(s => s.Self())
    .FontSources(s => s.Self()
        .CustomSources("https://fonts.gstatic.com")
    )
    .FrameAncestors(s => s.None())
    .FrameSources(s => s.None())
    .UpgradeInsecureRequests()
);

This results in the header shown below (new lines added for readability).

Content-Security-Policy: 
default-src 'self';
frame-src 'none';
font-src 'self' https://fonts.gstatic.com;
frame-ancestors 'none';
upgrade-insecure-requests;
block-all-mixed-content

Note that you must configure the CSP to suit your own web application by adding additional custom sources for every type of content which you need to load from an external source.

In the above example, the default sources are configured to ‘self’. This means that unless a more specific source setting is specified, then the browser will only load content from itself.

Font sources have been configured to allow fonts from ‘self’ and also from a custom source (Google Static Content). The same principle can be applied to images, scripts, styles or any other type of content.

In the example, I’ve included some additional security settings to block all mixed content (HTTPS content only) and there is an instruction to upgrade insecure requests to secure requests where needed.

I’m also configuring frame ancestors with the ‘none’ option to prevent the app from being embedded on other websites and I’m configuring frame sources with the ‘none’ option to prevent the app from displaying iframes.

The Report URI website features a user interface that allows you to build a CSP header for your website. This allows you to see all of the available sources and the valid options for each source type.

Middleware ordering

It is important when configuring your ASP.NET Core pipeline to consider the order in which you register middleware, including the point at which you configure security headers.

The order of middleware should be as follows.

  • Exception Handler
  • HSTS
  • HTTPS Redirection
  • Security Headers
  • Static Files
  • Cookie Policy
  • Routing
  • Request Localization
  • CORS
  • Authentication
  • Authorization
  • Session
  • Response Caching
  • Custom middlewares
  • Endpoints

Check out the Microsoft Docs for more information regarding the ordering of middleware components.

Security testing

Now that you’ve configured all of the security headers and published your web app, how do you verify that you’ve configured everything correctly?

Head over to the Security Headers website and enter your website URL to scan your app and get a security rating!

If everything is set up correctly you should get a rating of ‘A’ or better.

Additional security considerations

Before wrapping up, I wanted to briefly touch on some other areas of security. In addition to security headers, you should consider the security aspects covered in the following sub-sections.

Hide server details

Where possible, consider removing server details from your HTTP responses.

For example, if you are hosting your application using a Linux Web App in Azure, you can hide the ‘Kestrel’ server header as follows.

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
            webBuilder.ConfigureKestrel(o => o.AddServerHeader = false);
        });

This will prevent the server name from being returned as a HTTP header.

HTTPS Redirection & HSTS

Transport Layer Security should be enabled and enforced across your web application.

Redirecting all traffic to HTTPS and informing the browser that your web application should always be served over HTTPS are very important aspects of an effective Transport Layer Security configuration.

In my HTTPS Redirection blog article I cover how to configure HTTPS Redirection and HSTS for ASP.NET Core web apps.

Cookie Policy

Configure your cookie policy with the strictest possible settings where possible.

app.UseCookiePolicy(new CookiePolicyOptions
{
    HttpOnly              = HttpOnlyPolicy.Always,
    MinimumSameSitePolicy = SameSiteMode.Strict,
    Secure                = CookieSecurePolicy.Always
});

HTTP Only cookies can only be accessed via HTTP requests. This offers protection against XSS attacks since cookies will not be accessible via JavaScript.

Setting the Same Site Policy to ‘Strict’ prevents cookies from being forwarded to other websites which offers protection against CRSF (Cross Site Request Forgery) attacks.

Secure cookies will only ever be transmitted over HTTPS which is essential for security-sensitive cookies such as auth token cookies.

Anti-forgery tokens

To add further protection against CRSF attacks, anti-forgery tokens should be used for all HTTP form and AJAX post/delete requests.

Most frameworks, including ASP.NET Core, have built-in anti-forgery features.

For example, ASP.NET Core has an AutoValidateAntiforgeryTokenAttribute filter that can be configured globally.

Renaming the anti-forgery token cookie to avoid revealing tech stack info is also a good idea.

Other considerations

It goes without saying that there are many other aspects to security that need to be considered, such as SQL injection, DDoS (Distributed Denial of Services) attacks, mass assignment attacks, leaked exceptions, password security, the ‘Robots’ file and general permission issues.

However, I have covered what I believe to be the most important security features that should be enabled at a global, site-wide level.

The majority of the other security considerations (e.g. leaking sensitive information in HTML or script comments) are things that you need to make sure are taken care of in all areas of your application code.

Summary

In this article, we have considered the key security headers which you should be configuring in order to step up the security of your web applications.

We’ve looked at what the most secure settings are for each available header and how to configure them for an ASP.NET Core web app using NWebsec.

We’ve also touched on some other aspects of security that should be considered when developing modern web applications.

In closing, it is important to remember that security is a massive topic and it is important to keep up to date with the latest developments to ensure the best possible security for our applications.


I hope you enjoyed this post! Comments are always welcome and I respond to all questions.

If you like my content and it helped you out, please check out the button below 🙂

Comments