NWebsec - Security libraries for ASP.NET Core

Getting started with NWebsec

NWebsec lets you configure quite a few security headers, some are useful for most applications while others are a bit more specialized. Here’s some guidance to get you started.

First, you need to add NWebsec to your application. The easiest way to do this would be to get it through NuGet. Search for NWebsec in the package manager GUI, or install it through the console with one of the following commands:

For an ASP.NET core app:

Install-Package NWebsec.AspNetCore.Middleware

If you’d want to use the MVC attributes

Install-Package NWebsec.AspNetCore.Mvc

And if you’d want to use the MVC tag helpers

Install-Package NWebsec.AspNetCore.Mvc.TagHelpers

Now it’s time to start securing your application! It’s good practice to remove the version headers added by ASP.NET and IIS, so you’d want to suppress version headers for your web application. The NuGet installation procedure will make some modifications to the web.config to disable version headers.

To avoid various attacks carried out through iframes, the X-Frame-Options header should be enabled. MIME sniffing is a source to many problems, including security issues, so you’d want to run with the X-Content-Type-Options header.

For applications that run over SSL/TLS, you should most definitely employ the Strict-Transport-Security header — instructing the browser to interact with anything on your domain over a secured connection only.

Unless your application needs to redirect users to arbitrary sites on the internet, you’d want redirect validation enabled. There might be a few sites you’d want to whitelist for redirects, in particular if you use WIF or Google/Facebook/any other external authentication provider. Consult Redirect validation if you run into trouble.

So, for an application running over http the following is a reasonable starting point for based on the Startup.cs from the ASP.NET Core MVC webapp template:

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    //Registered before static files to always set header
    app.UseXContentTypeOptions();

    app.UseStaticFiles();

    //Registered after static files, to set headers for dynamic content.
    app.UseXfo(xfo => xfo.Deny());
    app.UseRedirectValidation(); //Register this earlier if there's middleware that might redirect.

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

If your site is served over https, you’d also want to include the Strict-Transport-Security header, as such (note that the browser will load all content over https for the entire domain when the header is used):

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    //Registered before static files to always set header
    app.UseHsts(hsts => hsts.MaxAge(365));
    app.UseXContentTypeOptions();

    app.UseStaticFiles();

    //Registered after static files, to set headers for dynamic content.
    app.UseXfo(xfo => xfo.Deny());
    app.UseRedirectValidation(); //Register this earlier if there's middleware that might redirect.

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Note! If users can log into you application, you should always run it over https to keep your users safe!

NWebsec lets you add other security headers as well, but these are more tightly coupled to the individual resources in your application. In particular, the Content-Security-Policy (CSP) header can significantly improve the security of a web application but also requires great care when you’re building a new application from the ground up — even more so if you retrofit it onto an existing application. SendSafely has published two blog posts discussing how they dealt with the challenge, links included for the interested reader:

See Configuring Content-Security-Policy to learn how to enable CSP, this is where the real job starts. Good luck! :)

Note also that security headers can be enabled through MVC attributes, refer to NWebsec.Mvc for details.

NWebsec libraries

NWebsec.AspNetCore.Middleware

NWebsec.AspNetCore.Middleware provides ASP.NET Core middleware that lets you output HTTP security headers. It currently supports:

  • Strict-Transport-Security
  • X-Content-Type-Options
  • X-Download-Options
  • X-Frame-Options
  • X-Xss-Protection
  • Content-Security-Policy
  • X-Robots-Tag

In addition, it provides middleware for redirect validation.

Documentation

The middleware is documented alongside the web.config and MVC attributes. Refer to the Configuration for samples.

NWebsec.AspNetCore.Mvc

NWebsec.AspNetCore.Mvc lets you configure NWebsec through MVC ActionFilter attributes. ASP.NET Core MVC is supported. By decorating controllers and actions with attributes, the NWebsec.AspNetCore.Middleware configuration can be overridden. Here’s the order in which the final configuration is calculated:

  1. Config for Middleware
  2. MVC global filter
  3. MVC controller
  4. MVC action

This gives a fair amount of flexibility. Do your stuff with middleware or in MVC, or both. You decide!

The attributes

Most elements from the Configuration have an MVC attribute counterpart.

Now back to the attributes — they are probably best explained in code. Here are the attributes registered as global filters in during startup for an MVC app:

...
using NWebsec.AspNetCore.Mvc;
using NWebsec.AspNetCore.Mvc.Csp;

....

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc(opts =>
    {
        opts.Filters.Add(typeof(NoCacheHttpHeadersAttribute));
        opts.Filters.Add(new XRobotsTagAttribute() { NoIndex = true, NoFollow = true });
        opts.Filters.Add(typeof(XContentTypeOptionsAttribute));
        opts.Filters.Add(typeof(XDownloadOptionsAttribute));
        opts.Filters.Add(typeof(XFrameOptionsAttribute));
        opts.Filters.Add(typeof(XXssProtectionAttribute));
        //CSP
        opts.Filters.Add(typeof(CspAttribute));
        opts.Filters.Add(new CspDefaultSrcAttribute { Self = true });
        opts.Filters.Add(new CspScriptSrcAttribute { Self = true });
        //CSPReportOnly
        opts.Filters.Add(typeof(CspReportOnlyAttribute));
        opts.Filters.Add(new CspScriptSrcReportOnlyAttribute { None = true });
    });
}

Here’s what the attributes will look like when applied to a controller and an action method:

using Microsoft.AspNetCore.Mvc;
using NWebsec.AspNetCore.Mvc;

...

[NoCacheHttpHeaders]
[XContentTypeOptions]
[XDownloadOptions]
[XFrameOptions(Policy = XFrameOptionsPolicy.SameOrigin)]
[XXssProtection]
public class HomeController : Controller
{
    [XFrameOptions(Policy = XFrameOptionsPolicy.Deny)]
    public IActionResult Index()
    {
        return View();
    }

    [NoCacheHttpHeaders(Enabled = false)]
    [XContentTypeOptions(Enabled = false)]
    [XDownloadOptions(Enabled = false)]
    [XFrameOptions(Policy = XFrameOptionsPolicy.Disabled)]
    [XXssProtection(Policy = XXssProtectionPolicy.Disabled)]
    [XRobotsTag(Enabled = false)]
    public IActionResult HeadersDisabled()
    {
        ViewData["Message"] = "Your application description page.";

        return View("About");
    }
}

Note how the Index action method is decorated with only one attribute. It has its own XFrameOptions setting, and will inherit all other attributes from the controller.

The HeadersDisabled action shows how headers can be disabled per action or controller. This lets you define a strict global security policy for your application and relax the policy where needed.

NWebsec Tag Helpers

NWebsec includes tag helpers to help generate CSP nonces. Import the tag helpers in the _ViewImports.cshtml, alongside those from Microsoft:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, NWebsec.AspNetCore.Mvc.TagHelpers

Then you can easily include a CSP nonce on script or style tags in your views:

<script nws-csp-add-nonce="true">
    alert('Nonce added');
</script>

<style nws-csp-add-nonce="true"></style>

NWebsec will generate nonces, add them to the CSP header, and include them in the markup:

<script nonce="buoc1h8zWh0qITh/RHq1">
    alert('Nonce added');
</script>
<style nonce="Gn&#x2B;BKIciW17bcffZtx5o"></style>

The nonce values will of course change between requests.

NWebsec is made up of several libraries, each offering different approaches to improve the security of your web applications.

NWebsec Middleware

NWebsec.AspNetCore.Middleware offers ASP.NET Core middleware for many of NWebsec’s features.

NWebsec for Mvc

NWebsec.AspNetCore.Mvc configures NWebsec through MVC attributes. This is useful when you need to do adjustments to the application wide config from NWebsec.AspNetCore.Middleware for particular controllers or actions.

NWebsec TagHelpers

NWebsec Tag Helpers lets you add content specific security settings, such as CSP nonces, straight in your Razr views.

Configuration

Configuring cache headers

Cache headers play an important role for your users’ privacy if you’re running a secure website. Browsers tend to cache web pages that the users loads, for easy retrieval and later display. Browser caching is an important mechanism to “speed up” up the Internet, to avoid having to fetch content that has not changed.

In short the browser can behave in two ways when it comes to caching. It could ask the server whether a resource has changed, if not it can simply redisplay the page it has stored in cache. The browser can send the server an If-Modified-Since header to achieve this.

Also, the browser can serve previously loaded pages directly from cache — without checking with the server whether the page has changed. This is a common behaviour when the user is navigating back and forth with the “Back” and “Forward” buttons in the browser.

You can read an excellent write-up on the issues related to browser cache and history on Opera’s Yngve Pettersen’s blog: Introducing Cache Contexts, or: Why the browser does not know you are logged out.

To instruct the browser to reload pages when the user is navigating with the back and forward buttons you can configure NWebsec to set the following headers:

Cache-Control: no-cache, no-store, must-revalidate
Expires: -1
Pragma: no-cache

Warning

Setting these headers will make the browser reload every page in the browsing history when the user navigates with the “Back” and “Forward” buttons. This will affect the load on your server(s) — and also the user experience. Do not enable these headers unless you really have to.

Middleware

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
        ...

    app.UseStaticFiles();

    app.UseNoCacheHttpHeaders(); //Registered after static files, to set headers only for dynamic content.

    app.UseMvc(...);
}

Note

Enabling the no cache headers in middleware is a point of no return. If you want to enable these headers globally for your app but make exceptions for some of your controllers/actions, use a global MVC filter instead, as per the following example.

MVC filter

    ...
using NWebsec.AspNetCore.Mvc;
using NWebsec.AspNetCore.Mvc.Csp;

....

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc(opts =>
    {
        opts.Filters.Add(typeof(NoCacheHttpHeadersAttribute));
    });
}

You can also set the attribute on controllers and actions, see NWebsec.AspNetCore.Mvc.

Redirect validation

If you’re familiar with the OWASP top ten list you’ll know that Unvalidated Redirects and Forwards has been lingering as number ten on that list the last couple of years. These types of vulnerabilities are used in real attacks but fortunately there are several ways to deal with them. You’ll find a nice write-up on the issues and potential countermeasures here: OWASP Top 10 for .NET developers part 10: Unvalidated Redirects and Forwards.

The proper remedy for unvalidated redirects lie in the code. Whenever an application redirects the user based on input parameters, that input should be validated. As we all know, developers slip once in a while. Legacy applications can also pose a challenge, with code written during a time where security might not have been a priority. Having a “safety net” to help detect and fix such vulnerabilities makes it easier to keep the code base secure.

Redirect validation is slightly opportunistic. In most cases, the redirect validation exception will be thrown before the response headers are sent to the client. However, if the response is flushed early the headers will be sent to the client immediately. An exception will be thrown in any case if a non-whitelisted redirect is detected.

Configuring redirect validation

NWebsec.AspNetCore.Middleware lets you enable redirect validation with a single line in your startup class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        ...

        app.UseStaticFiles();

        app.UseRedirectValidation(); //Registered after static files, they don't redirect

        app.UseMvc(...);
    }

This configuration will validate all HTTP responses with a status code of 3xx, except 304 (Not Modified). Redirects to relative URIs are allowed, as well as redirects to the same site — meaning the same scheme/host/port. Redirects to other destinations will trigger a RedirectValidationException, terminating the response.

Note

Make sure to register the redirect validation middleware reasonably early in the pipeline. The middleware will not be called if a preceding middleware redirects and terminates the pipeline.

Web applications often redirect users from HTTP to HTTPS, this must be explicitly allowed. If the site is running on a non-default HTTPS port, one or more port numbers must be configured. Also, if your application needs to redirect to other sites they can be added to a whitelist in config, as such:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        ...

        app.UseStaticFiles();

        app.UseRedirectValidation(opts =>
        {
            opts.AllowSameHostRedirectsToHttps();
            //opts.AllowSameHostRedirectsToHttps(4430); Allow redirects to custom HTTPS port
            opts.AllowedDestinations("http://www.nwebsec.com/", "https://www.google.com/accounts/");
        });

        app.UseMvc(...);
    }

In addition to same site redirects, this would allow redirects to anywhere on http://www.nwebsec.com/ as well as https://www.google.com/accounts/ and subpaths. Redirects to other destinations will trigger an exception.

As an example based on the above configuration, a redirect to https://www.google.com/accounts/foo/bar would be allowed but a redirect to https://www.google.com/foo/ would raise an exception.

Configuring Content-Security-Policy

Content-Security-Policy (CSP) provides a safety net for injection attacks by specifying a whitelist from where various content in a webpage can be loaded from.

If you’re unfamiliar with CSP you should read An Introduction to Content Security Policy by Mike West, one of the Chrome developers. You’ll also find information about CSP on the Mozilla Developer Network.

A page’s content security policy is set through the following headers:

  • Content-Security-Policy
  • Content-Security-Policy-Report-Only

CSP support in NWebsec

You should read this entire article to understand how CSP configuration is inherited and/or overridden in NWebsec. We’ll start with a general introduction, and then move on to the configuration section and the MVC attributes.

CSP configuration

NWebsec emits the CSP header if CSP is enabled and one or more directives are configured — except for redirects and static content. The directives specified in CSP 1.0 are:

  • default-src — Specifies the default for other sources
  • script-src
  • style-src
  • object-src
  • img-src
  • media-src
  • frame-src
  • font-src
  • connect-src
  • sandbox (optional to implement)
  • report-uri — Specifies where CSP violations can be reported

CSP level 2 adds quite a few new directives over these, currently supported by NWebsec are:

  • frame-ancestors
  • base-uri
  • child-src
  • form-action
  • sandbox (no longer optional)

CSP 2 also introduces script and style hashes and nonces. You’ll find a good write-up on this on the Mozilla blog.

CSP level 3 adds quite a few new directives over these, currently supported by NWebsec are:

  • manifest-src
  • block-all-mixed-content

Upgrade Insecure Requests adds another CSP directive, see Upgrade Insecure Requests for details.

To use a directive, it must be configured with at least one source. The standard specifies some special sources.

  • ‘none’ — No content of this type is allowed
    • Supported by all directives
  • ‘self’ — Content of this type can only be loaded from the same origin (no content from other sites)
    • Supported by all directives
  • ‘unsafe-inline’ — Allows unsafe inline content.
    • Supported by style-src (inline css) and script-src (inline script)
  • ‘unsafe-eval’ — Allows script functions considered unsafe (such as eval())
    • Supported by script-src

You can also specify your own sources, in various formats specified by the standard. Here are a few examples.

  • * — Allow content from anywhere
  • https: — Scheme only, load only content served over https
  • *.nwebsec.com — Wildcard host, allow content from any nwebsec.com sub-domain.
  • www.nwebsec.com:81 — You can specify a port number
  • https://www.nwebsec.com — You can of specify an absolute URI for a host (path has no effect though)

NWebsec validates the configured sources and will let you know if something is wrong.

CSP 2 specifies support for internationalized domain names in custom sources.

Report-Only mode

The CSP standard actually defines two headers: Content-Security-Policy and Content-Security-Policy-Report-Only. Browsers will enforce the CSP when they see the first header, i.e. they will not load content that violates the policy and report the violation. If you use the Report-Only header, CSP will not be enforced by the browser, so all content will be loaded but violations will still be reported.

NWebsec lets you configure these headers independently so you can use one or the other, or both.

Configuring CSP middleware

The NWebsec.AspNetCore.Middleware package includes CSP middleware. Here’s an example of how you register the middleware in the OWIN startup class:

using NWebsec.Owin;
...
public void Configuration(IAppBuilder app)
{
    app.UseCsp(options => options
        .DefaultSources(s => s.Self())
        .ScriptSources(s => s.Self().CustomSources("scripts.nwebsec.com"))
        .ReportUris(r => r.Uris("/report")));

        app.UseCspReportOnly(options => options
            .DefaultSources(s => s.Self())
            .ImageSources(s => s.None()));
    }

Script and style nonces through tag helpers

The NWebsec Tag Helpers package includes Tag helpers to add CSP 2 script and style nonces to allow inline scripts/styles. The helpers will output the complete nonce-attribute. Here is an example of usage:

<script nws-csp-add-nonce="true">document.write("Hello world")</script>
<style nws-csp-add-nonce="true">
   h1 {
          font-size: 10em;
        }
</style>

Configuring CSP through MVC attributes

The NWebsec.AspNetCore.Mvc package provides MVC attributes to configure the security headers. The CSP policy defined by the MVC attributes are overridden per directive, this aligns with how this works in the web.config. That means that you define your baseline policy in web.config, CSP middleware or through global filters, and you can easily override a particular directive on a controller or action.

Here’s an example. You can e.g. enable CSP, and register directives through global filters:

...
using NWebsec.AspNetCore.Mvc;
using NWebsec.AspNetCore.Mvc.Csp;

....

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc(opts =>
    {
        opts.Filters.Add(typeof(CspAttribute));
        opts.Filters.Add(new CspDefaultSrcAttribute { Self = true });

        //CSPReportOnly
        //opts.Filters.Add(typeof(CspReportOnlyAttribute));
        //opts.Filters.Add(new CspScriptSrcReportOnlyAttribute { None = true });
    });
}

And consider the following controller:

[CspScriptSrc(Self = true, CustomSources = "scripts.nwebsec.com")]
public class HomeController : Controller

{
    public IActionResult Index()
    {
        return View();
    }

    [CspDefaultSrc(CustomSources = "nwebsec.com")]
    public IActionResult Index2()
    {
        return View();
    }

    [CspDefaultSrc(CustomSources = "stuff.nwebsec.com")]
    [CspScriptSrc(CustomSources = "scripts.nwebsec.com ajax.googleapis.com")]
    public IActionResult Index3()
    {
        return View();
    }
}

The index action will inherit the global attribute as well as the attribute set on the controller, which yields this header:

Content-Security-Policy: default-src 'self'; script-src 'self' scripts.nwebsec.com

The index2 action inherits previous directives yielding:

Content-Security-Policy: default-src 'self' nwebsec.com; script-src 'self' scripts.nwebsec.com

The index3 action also inherits all directives, thus giving us this header:

Content-Security-Policy: default-src 'self' stuff.nwebsec.com; script-src 'self' scripts.nwebsec.com ajax.googleapis.com

To have a directive completely removed, disable it as such:

[CspScriptSrc(Enabled = false)]

You can also disable CSP altogether:

[Csp(Enabled = false)]

Upgrade Insecure Requests

upgrade-insecure-requests is the latest addition to the CSP directives, but note that it lives in a separate specification. Though it looks very innocent and straight forward to configure, there are some important considerations before employing it.

The upgrade insecure requests CSP directive instructs browsers to upgrade all requests triggered by a page from HTTP to HTTPS. This lets site owners move legacy sites from HTTP to HTTPS without having to change every single link, and references to images, scripts and other content from HTTP to HTTPS to avoid mixed content issues.

Note

Unless you’re tasked with moving a legacy site that has been running on HTTP to HTTPS, and it has a bunch of hard coded references to HTTP resources on third party sites, upgrade-insecure-requests is not what you want. For new sites, the upcoming Mixed Content specification would be a better fit.

How it works

Say you have a site that has been running on http://www.example.com for years, and out of concern for your users’ privacy you want to make the site available on https://www.example.com. You realize there’s a daunting problem, http://www.example.com loads subresources (images, scripts etc.) from third party sites, these are all loaded over http:// and there’s no easy way to change all those references to point to https://. These resources are regarded mixed content as soon as you move to https://www.example.com. The content is available over https:// on the third party sites, it’s just that your’re not able to update every reference to it.

This is where upgrade-insecure-requests comes to the rescue. For browsers that support it, you can redirect users to https://www.example.com, and include a Content-Security-Policy: upgrade-insecure-requests header in the response. Conformant browsers will then load the page’s resources from both same origin and third party sites over https://, and the mixed content problem goes away. The page and its subresources are all loaded over a (more) secure connection.

You need to detect browser support for the upgrade-insecure-requests CSP directive. Redirecting non-conforming browsers to https:// will result in the mixed content issues. Fortunately, browsers will advertise their support through a request header:

Upgrade-Insecure-Requests: 1

When you enable the upgrade-insecure-requests directive in NWebsec, conforming browsers will be redirected to https with a 307 status code in accordance with the spec. The directive will always be included in the CSP header, as non-conforming browsers will ignore it.

This lets you gracefully move a legacy site from http to https as browser support for upgrade-insecure-requests improves over time.

Strict-Transport-Security

As users are moved to https on your site, the Strict-Transport-Security (HSTS) header can ensure that you keep them there. NWebsec supports setting the header only for UAs that support upgrade-insecure-requests. This HSTS setting is useful in combination with upgrade-insecure-requests, see Configuring Strict-Transport-Security for details.

Configuring Upgrade Insecure Requests

NWebsec.AspNetCore.Middleware lets you enable upgrade-insecure-requests through your OWIN startup class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...

    app.UseStaticFiles();

    app.UseCsp(opts => opts.UpgradeInsecureRequests());

    app.UseMvc(...);
}

Note

Remember to allow “same host redirects” if you’re also using Redirect validation.

Also note that NWebsec relies on the Request.IsHttps property when determining whether to redirect from http to https. If you are behind a load balancer/reverse proxy that terminates the user’s https connection you must ensure that these properties are set correctly.

Configuring Strict-Transport-Security

There are five configuration options:

  • max-age is a TimeSpan (see TimeSpan.Parse)
  • includeSubdomains adds includeSubDomains in the header, defaults to false
  • preload adds the preload directive, defaults to false. Max-age must be at least 18 weeks, and includeSubdomains must be enabled to use the preload directive. See the Chromium HSTS docs for details.
  • httpsOnly ensures that the HSTS header is set over secure connections only, defaults to true.
  • upgradeInsecureRequests sets the HSTS header only for UAs that supports Upgrade Insecure Requests. This setting cannot be combined with preload.

Note

upgradeInsecureRequests is intended to be used in combination with the Upgrade Insecure Requests CSP directive.

Configuration Resulting header
max-age=”00:00:00” Strict-Transport-Security: max-age=0
max-age=”12:00:00” Strict-Transport-Security: max-age=43200
max-age=”365” includeSubdomains=”true” Strict-Transport-Security: max-age=31536000; includeSubDomains
max-age=”365” includeSubdomains=”true” preload=”true” Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Register the middleware in the startup class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseHsts(options => options.MaxAge(days: 30).IncludeSubdomains());
    //app.UseHsts(options => options.MaxAge(days:365).IncludeSubdomains().Preload());
    //app.UseHsts(options => options.MaxAge(days:365).UpgradeInsecureRequests());

    app.UseStaticFiles();

    app.UseMvc(...);
}

Configuring Public-Key-Pins

There are four configuration options, as well as a list of certs to pin and/or a list of pin values. Note that you must supply two pins to generate a valid header, i.e. two certs, a cert and a pin value, or two pin values.

  • max-age is a TimeSpan (see TimeSpan.Parse)
  • includeSubdomains adds includeSubDomains in the header, defaults to false
  • httpsOnly ensures that the HSTS header is set over secure connections only, defaults to true.
  • reportUri specifies an absolute URI to where the browser can report HPKP violations. The scheme must be HTTP or HTTPS.
  • certificates specifies a list of certificates (by thumbprints) that should be pinned.
  • pins specifies a list of pinning values for certificates that should be pinned

The following examples assume that we supply the pinning values:

  • n3dNcH43TClpDuyYl55EwbTTAuj4T7IloK4GNaH1bnE=
  • d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=
Configuration Resulting header
max-age=”00:00:00” Public-Key-Pins: max-age=0
max-age=”12:00:00” Public-Key-Pins: max-age=43200;pin-sha256=”n3dNcH43TClpDuyYl55EwbTTAuj4T7IloK4GNaH1bnE=”;pin-sha256=”d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=”
max-age=”365”
includeSubdomains=”true”
Public-Key-Pins: max-age=31536000;includeSubdomains;pin-sha256=”n3dNcH43TClpDuyYl55EwbTTAuj4T7IloK4GNaH1bnE=”;pin-sha256=”d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=”
max-age=”365”
includeSubdomains=”true”
report-uri=”https://report.nwebsec.com/
Public-Key-Pins: max-age=31536000;includeSubdomains;pin-sha256=”n3dNcH43TClpDuyYl55EwbTTAuj4T7IloK4GNaH1bnE=”;pin-sha256=”d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=”;report-uri=”https://report.nwebsec.com/” |

Register the middleware in the startup class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseHpkp(options => options
    .MaxAge(seconds: 20)
    .Sha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
    .PinCertificate("FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF")
    .ReportUri("https://nwebsec.com/report")
    );

    app.UseStaticFiles();

    app.UseMvc(...);
}

Configuring X-Frame-Options

This header can be configured in three ways:

Configuration Resulting header
policy=”Disabled” None
policy=”Deny” X-Frame-Options: Deny
policy=”SameOrigin” X-Frame-Options: SameOrigin

Register the middleware in the startup class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...

    app.UseStaticFiles();

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

    app.UseMvc(...);
}

As an MVC attribute, defaults to policy=”Deny”:

[XFrameOptions]
[XFrameOptions(Policy = XFrameOptionsPolicy.SameOrigin)]

The header is omitted for redirects.

Configuring X-XSS-Protection

There are two configuration options

  • policy can be set to :
    • Disabled
    • FilterDisabled
    • FilterEnabled
  • blockMode adds mode=block in the header, defaults to false
Configuration Resulting header
policy=”Disabled” None
policy=”FilterDisabled” X-XSS-Protection: 0
policy=”FilterEnabled” blockMode=”true” X-XSS-Protection: 1; mode=block

Register the middleware in the startup class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        ...

        app.UseStaticFiles();

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

        app.UseMvc(...);
    }

Or as an MVC attribute, defaults to “FilterDisabled” blockMode=”true”:

[XXssProtection]
[XXssProtection(Policy = XXssProtectionPolicy.Disabled)]

The header is omitted for redirects and static content.

Configuring X-Content-Type-Options

There are two settings:

Configuration Resulting header
enabled=”false” None
enabled=”true” X-Content-Type-Options: nosniff

Register the middleware in the OWIN startup class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...

    app.UseStaticFiles();

    app.UseXContentTypeOptions();

    app.UseMvc(...);
}

Or as an MVC attribute (which defaults to true):

[XContentTypeOptions]
[XContentTypeOptions(Enabled = false)]

The header is omitted for redirects.

Configuring X-Download-Options

There are two settings:

Configuration Resulting header
enabled=”false” None
enabled=”true” X-Download-Options: noopen

Register the middleware in the OWIN startup class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...

    app.UseStaticFiles();

    app.UseXDownloadOptions();

    app.UseMvc(...);
}

Or as an MVC attribute (which defaults to true):

[XDownloadOptions]
[XDownloadOptions(Enabled = false)]

The header is omitted for redirects.

Configuring X-Robots-Tag

You might be familiar with the Robots Exclusion Protocol (REP), often communicated by a robots.txt file on the root of a site or through meta tags in HTML such as:

<meta name="robots" content="noindex, nofollow">

REP gives some control over which content search engines will index on your site, i.e. for search engines who respect REP. Remember, you are politely asking search engines to not index your content — but not all search engines are that polite.

In addition to robots.txt and meta tags, some of the major search engines support REP through an HTTP header. The following header is the equivalent to the aforementioned meta tag:

X-Robots-Tag: noindex, nofollow

Using the HTTP header can be a nice alternative to the robots.txt and the meta tags — especially for content other than html such as PDF, XML or Office files. The header is supported by Bing and Google, refer to these two resources for the nitty gritty details:

NWebsec lets you emit the X-Robots-Tag header as of version 2.1.0. The directives supported are:

  • noindex - Instructs search engines to not index the page
  • nofollow - Instructs search engines to not follow links on the page
  • nosnippet - Instructs search engines to not display a snippet for the page in search results
  • noarchive - Instructs search engines to not offer a cached version of the page in search results
  • noodp - Instructs search engines to not use information from the Open Directory Project for the page’s title or snippet
  • notranslate - Instructs search engines to not offer translation of the page in search results (Google only)
  • noimageindex - Instructs search engines to not index images on the page (Google only)

There are several ways to enable the X-Robots-Tag header:

With NWebsec.AspNetCore.Middleware, you register the middleware in the startup class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...

    app.UseStaticFiles();

    app.UseXRobotsTag(options => options.NoIndex().NoFollow());

    app.UseMvc(...);
}

NWebsec.AspNetCore.Mvc lets you register an MVC filter:

...
using NWebsec.AspNetCore.Mvc;
using NWebsec.AspNetCore.Mvc.Csp;

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc(opts =>
    {
        opts.Filters.Add(new XRobotsTagAttribute() { NoIndex = true, NoFollow = true });
    });
}

You can also set the attribute on controllers and actions, see NWebsec.AspNetCore.Mvc for details.

NWebsec features can be enabled through middleware or MVC filter attributes. Refer to each security header/feature in the menu for details.

Breaking changes

Every now an then there needs to be some breaking changes between NWebsec versions. You’ll find them documented here.

Since the NWebsec.AspNetCore packages have not been released in version 1.0 yet, we haven’t started counting breaking changes yet. :)

NWebsec and the SDL

You might be familiar with Microsoft’s Security Development Lifecycle (SDL) — a software security assurance process. The SDL is how Microsoft ensures that security is taken care of throughout their development processes. It’s broken down in sections for different development phases and each section contains requirements and recommendations to ensure both security and privacy in their software. They’ve published the process guidance to aid others in introducing security activities in their own development processes.

Many of the requirements and recommendations are concrete and actionable — this page lists the SDL requirements that NWebsec will help you fulfill (the others you’ll have to take care of yourself #kthxbai :).

These requirements are from the SDL Process Guidance Version 5.2 released May 23, 2012.

Session fixation

From Phase Two Design, Security Recommendations:

Strong log-out and session management. Proper session handling is one of the most important parts of web application security. At the most fundamental level, sessions must be initiated, managed, and terminated in a secure manner. If a product employs an authenticated session, it must begin as an encrypted authentication event to avoid session fixation.

You need to take care of the encryption by running your web application over TLS, but session fixation is taken care of when you combine that recommendation with:

Authentication events must invalidate unauthenticated sessions and create a new session identifier.

[[NWebsec.SessionSecurity]] ensures that unauthenticated sessions IDs aren’t reused for authenticated sessions and prevents session fixation attacks through the means of [[Authenticated session identifiers]].

The X-Download-Options header

From Phase Two Design, Security Recommendations:

Apply no-open header to user-supplied downloadable files. Use the HTTP Header X-Download-Options: noopen for each HTTP file download response that may contain user-controllable content. Recommended tool: Casaba Passive Security Auditor.

See Configuring X-Download-Options to let NWebsec add this header for you.

The X-Content-Type-Options header

From Phase Three Implementation. Security Requirements:

Internet Explorer 8 MIME handling: Sniffing OPT-OUT. This recommendation addresses functionality new in Internet Explorer 8 that may have security implications in some cases. It is recommended that for each HTTP response that could contain user controllable content, you utilize the HTTP Header X-Content-Type-Options:nosniff. The Watcher tool may be of use in meeting this requirement.

See Configuring X-Content-Type-Options to let NWebsec add this header for you.

The Content-Security-Policy header

From Phase Three Implementation. Security Recommendations:

Do not use the JavaScript eval() function (or equivalents). The JavaScript eval() function is used to interpret a string as executable code. While eval() enables a web application to dynamically generate and execute JavaScript (including JSON), it also opens up potential security holes, such as injection attacks, where an attacker-fed string may also get executed. For this reason, the eval() function or functional equivalents, such as setTimeout() and setInterval(), should not be used.

See Configuring Content-Security-Policy to let NWebsec add this header for you — CSP will disable all these JavaScript functions (see the script-src directive in section 4.2 of the CSP specification).

The X-Frame-Options header

From Phase Three Implementation. Security Recommendations:

ClickJacking defense. For each page that could contain user controllable content, you should use a “frame-breaker” script and include the HTTP response header named X-FRAME-OPTIONS in each authenticated page. The Watcher tool may be of use in meeting this recommendation. The exit criteria for this recommendation is as follows:

  1. A “frame-breaker” script is included in each authenticated page to prevent unintentionally framing.
  2. The X-FRAME-OPTIONS header has been added to all authenticated page HTTP responses that should not be framed (for example, DENY) or is utilized to only allow trusted sites to frame site content (for example, the current site with the use of SAMEORIGIN).

See Configuring X-Frame-Options to let NWebsec add this header for you. Also remember that you need to take care of the “frame-breaker” script to fully meet this requirement.

Redirect validation

From Phase Three Implementation. Security Requirements:

Safe redirect, online only. Automatically redirecting the user (through Response.Redirect, for example) to any arbitrary location specified in the request (such as a query string parameter) could open the user to phishing attacks. Therefore, it is recommended that you not allow HTTP redirects to arbitrary user-defined domains.

See Redirect validation to add enable the NWebsec safety net for unvalidated redirect vulnerabilities.

ASP.NET Core vs ASP.NET 4

NWebsec saw the light of day back in 2012 and initially included an HttpModule that set security headers for ASP.NET applications. NWebsec (still) targets both .NET 3.5 and .NET 4. NWebsec.Mvc later introduced MVC filter attributes. NWebsec.Owin was created after Microsoft released their Katana project, bringing the OWIN ideas to ASP.NET 4.

ASP.NET Core, a major redesign and rewrite of ASP.NET, emerged as the “new” ASP.NET during 2016. Consequently, the NWebsec.AspNetCore packages were released in early versions. These packages target up-to-date versions of the .NET framework, the new abstractions in ASP.NET Core, and has no ties to the “old” ASP.NET.

Efforts will be focused on the NWebsec.AspNetCore libraries in the future. The number of downloads for these packages are quickly rising and is expected to outgrow the “old” packages in the near future. With Visual Studio 2017, ASP.NET Core projects will be a natural selection for new web applications since the changes to the project files are over. With stability in the project system, stability will come to the (3’rd party) tooling as well.

This does not mean that the “old” version of NWebsec is dead in any way. The library will be maintained, so if there should surface any bugs those would be considered seriously. But new features will be included in the ASP.NET Core libraries, and will not on a regular basis be backported to the old libraries.

NWebsec consists of several security libraries for ASP.NET applications. These libraries work together to remove version headers, control cache headers, stop potentially dangerous redirects, and set important security headers. If you’re not sure what “security headers” are, check out this blog post: Security through HTTP response headers.

After the introduction of ASP.NET core, there are two sets of NWebsec packages. You’ve now found the documentation for the “new” packages built for ASP.NET Core:

NWebsec for ASP.NET 4

Historically, NWebsec has been targeting ASP.NET 4. The following packages target ASP.NET 4:

Documentation for these packages is maintained separately as the aspnet4 version of the docs.

There’s also a dedicated session security library documented as a separate project.

NWebsec.AzureStartupTasks

In addition to the ASP.NET libraries, there’s also a package that helps you harden the TLS configuration for Azure web role instances:

Learn why you need to harden the default TLS configuration in the blog post Hardening Windows Server 2008/2012 and Azure SSL/TLS configuration.

Check out the NWebsec demo site to see the headers and session security improvements in action.

To keep up with new releases or to give feedback, find @NWebsec on Twitter. You can also get in touch at nwebsec (at) nwebsec (dot) com.