Secure Web Application Using HTTP Security Headers In ASP.NET Core

The security headers help protect against some of the attacks which can be executed against a website. It instructs the browser to enable or disable certain security features while the server response is being rendered to browser. This article demonstrates how to add headers in a HTTP response for an ASP.NET Core application in the easiest way.
 
The response HTTP headers could be set at either the application or web server level however care should be taken as some of the headers could limit application functionality. Ideally, all changes made should be implemented in a test environment before being deployed to production.
 
Here, I will use NWebsec, an ASP.NET security library that lets you easily configure these headers for your ASP.NET Core Website or WebApi application pipeline.
 
Below are the most commonly used security headers that we will implement in this article,
  1. X-XSS-Protection
  2. Content-Security-Policy
  3. X-Frame-Options
  4. Strict-Transport-Security (HSTS)
  5. X-Content-Type-Options
  6. Referrer-Policy
  7. Feature Policy
  8. X-Powered-By and Server
I have created a sample web application in ASP.NET Core and deployed it to Azure app services. As I have deployed the website, I did a quick scan on https://securityheaders.com/ to check what headers are actually missing for my website and scope of improvement. 
 
 
A couple of headers are missing which are highlighted in red.
 
In case you don’t have a deployed site, and if you want check the same in localhost, let’s run the application and check on the network tab of your browser.
 
 
We can see that the Strict-Transport-Security header is not there. This is because we are running on localhost. Also we are exposing server info (IIS/10.0) as well as application information like ASP.NET. We should not expose this information to anonymous users for security reasons.
 

Implementing Http Security headers in ASP.NET Core

 
Install NuGet Package NWebsec.AspNetCore.Middleware into project.
 
 
Let’s configure one by one in Startup.cs class in the Configure method.
 
X-XSS-Protection Header
 
This header is used to prevent cross-site scripting attack. Most of the modern browsers to stop loading the page, when a XSS is identified. Add below code to Configure method.
 
To implement this header, add below code to Configure method in Startup.cs. 
  1. app.UseXXssProtection(options => options. EnabledWithBlockMode());  
If you set EnabledWithBlockMode, the page will not be displayed at all in the browser.
 
Content-Security-Policy Header
 
This header helps to prevent code injection attacks like cross-site scripting and clickjacking or prevent mixed mode (HTTPS and HTTP). We can disable execution of inline scripts in webpages if required and we need to explicitly specify a Custom Sources rom where our webpages are allowed to load scripts and other content.
 
This configuration is very much dependent upon specific requirements and has some flexibility on what we need to allow and what not to allow.
 
There are four main keywords which we generally used while defining CSP,
  • None() means nothing will be loaded
  • Self() means load things from the same domain as the page was served, i.e. same scheme, host, port
  • UnsafeInline() means enables execution of inline and possibly insecure scripts/styles. But this is not recommended to use.
  • UnsafeEval() means enables execution of eval and other risky functions. Again this is also not recommended to use.
To implement this header, add the below code to Configure method in Startup.cs.(this may vary depending upon your requirement)
  1. app.UseCsp(opts => opts  
  2.       .BlockAllMixedContent()  
  3.                     .DefaultSources(s => s.Self())  
  4.                     .ScriptSources(s => s.Self())  
  5.                     .ScriptSources(s => s.UnsafeInline())  
  6.                     .ScriptSources(s => s.CustomSources("https://ajax.aspnetcdn.com"))  
  7.                     .StyleSources(s => s.Self())  
  8.                     .StyleSources(s => s.UnsafeInline())  
  9.                     .StyleSources(s => s.CustomSources("https://fonts.googleapis.com"))  
  10.                     .FontSources(s => s.Self())  
  11.                     .FontSources(s => s.CustomSources("https://fonts.gstatic.com"))  
  12.                 );  
Here, we are allowing public CDNs like https://ajax.aspnetcdn.com by using custom source. Similarly, we are allowing custom Google fonts and style.
 
During implementaion if you see any console error as like below in the browser, you need to modify the CSP rule based on that. That’s why here I added .StyleSources(s => s.CustomSources("https://fonts.googleapis.com")) and .FontSources(s => s.CustomSources ("https://fonts.gstatic.com"))
 
It refused to load the stylesheet 'https://fonts.googleapis.com/css?family=Roboto&display=swap' because it violates the following Content Security Policy directive: "style-src 'self' 'unsafe-inline'". Note that 'style-src-elem' was not explicitly set, so 'style-src' is used as a fallback.
 
 
Also to implement CSP nonce, Import the NWebsec tag helpers in the _ViewImports.cshtml to allow inline scripts/styles.
  1. @addTagHelper *, NWebsec.AspNetCore.Mvc.TagHelpers   
Then you need to include nws-csp-add-nonce="true" tag on script or style tags in your views.
  1. <script nws-csp-add-nonce="true"></script>  
  2. <style nws-csp-add-nonce="true"></style>  
X-Frame-Options Header
 
This header is used to ensure that website content is not embedded into other sites and to prevent click jacking attacks. This header will block iframe (i.e. <frame>, <iframe>, <embed> or <object) which is running by hackers.
 
Clickjacking attack is nothing but a malicious website that will load on top of your site in a iframe which is not visible to the user. When the user clicks on actual links, button on the main website, the click is actually done for unintended links in the hidden iframe.
 
To implement this header, add the below code to Configure method in Startup.cs.
  1. app.UseXfo(options => options.Deny());  
Strict-Transport-Security Header
 
This header used to enforce that all communication is done over HTTPS. This will protect websites against SSL stripping, man-in-the-middle attacks by indicating to the browser to access the website using HTTPS instead of using HTTP and refuse to connect in case of certificate errors and warnings. ASP.NET Core already comes with middleware named HSTS and it’s recommended to use HSTS on production only.
  1. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)    
  2.         {    
  3.             if (env.IsDevelopment())    
  4.             {    
  5.                 app.UseDeveloperExceptionPage();    
  6.             }    
  7.             else    
  8.             {    
  9.                 app.UseExceptionHandler("/Error");    
  10.                 // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.    
  11.                 app.UseHsts();    
  12.             }    
  13. }     
To modify default value, add below into ConfigureServices method
  1. services.AddHsts(options =>    
  2. {    
  3.    options.IncludeSubDomains = true;    
  4.    options.MaxAge = TimeSpan.FromDays(365);    
  5. });     
Here, max-age tells the browser for how many seconds it should enforce the policy and includeSubdomains indicates whether the policy should also be applied to subdomains.
 
X-Content-Type-Options Header
 
This header is used to disable the MIME-sniffing (where a hacker tries to exploit missing metadata on served files in browser) and can be set to no-sniff to prevent it.
  1. app.UseXContentTypeOptions();  
Referrer Policy Header
 
This header contains a site from which the user has been transferred. But referrer URLs may contain sensitive data. If you don’t want to allow browsers to display your website as last visited in “Referer” header, we can use Referrer-Policy: no-referrer.
  1. app.UseReferrerPolicy(options => options.NoReferrer());  
Feature Policy Header
 
This header is used to enable and disable certain web platform features like microphone, camera etc. on browser and those they embedded. This header is completely optional and based on the explicit requirement. This can be achieved though, as like below as there no such middleware as part of NWebsec package.
  1. app.Use(async (context, next) =>    
  2. {    
  3.    if (!context.Response.Headers.ContainsKey("Feature-Policy"))    
  4.    {    
  5.       context.Response.Headers.Add("Feature-Policy""accelerometer 'none'; camera 'none'; microphone 'none';");    
  6.    }    
  7.   await next();    
  8. });    
Remove X-Powered-By and Server Headers
 
It is not recommended to leak the server type and version number (i.e. ASP.NET, Kestrel, IIS) to an anonymous client. We can remove X-Powered-By header by adding to web.config. Without adding web.config in your project, we cannot remove this header as there are no such middlewares and this has been added by the web server.
  1. <configuration>    
  2.   <system.webServer>    
  3.     <httpProtocol>    
  4.       <customHeaders>    
  5.         <remove name="X-Powered-By"/>    
  6.       </customHeaders>    
  7.     </httpProtocol>    
  8.   </system.webServer>    
  9. </configuration>    
To disable the Server header for Kestrel, you need to set AddServerHeader to false in Program.cs
  1. public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>    
  2.             WebHost.CreateDefaultBuilder(args)    
  3.                 .UseKestrel(opt => opt.AddServerHeader = false)    
  4.                 .UseStartup<Startup>();     
In case of IIS, we can do it through web.config.
  1. <configuration>    
  2.   <system.webServer>    
  3.    <security>    
  4.       <requestFiltering removeServerHeader="true" />    
  5.     </security>    
  6.   </system.webServer>    
  7. </configuration>    
So, we are done with implementation, Let’s see the complete changes at a glance.
 
Configure Method in Startup.cs
  1. app.UseReferrerPolicy(options => options.NoReferrer());    
  2. app.UseXContentTypeOptions();    
  3. app.UseXXssProtection(options => options.EnabledWithBlockMode());    
  4. app.UseXfo(options => options.Deny());    
  5. app.UseCsp(options => options    
  6.                     .BlockAllMixedContent()    
  7.                     .DefaultSources(s => s.Self())    
  8.                     .ScriptSources(s => s.Self().UnsafeInline().CustomSources("https://ajax.aspnetcdn.com"))    
  9.                     .StyleSources(s => s.Self())    
  10.                     .StyleSources(s => s.UnsafeInline())    
  11.                     .StyleSources(s => s.CustomSources("https://fonts.googleapis.com"))    
  12.                     .FontSources(s => s.Self())    
  13.                     .FontSources(s => s.CustomSources("https://fonts.gstatic.com"))    
  14.                 );    
  15.     
  16. app.Use(async (context, next) =>    
  17.  {    
  18.     if (!context.Response.Headers.ContainsKey("Feature-Policy"))    
  19.      {    
  20.        context.Response.Headers.Add("Feature-Policy""accelerometer 'none'; camera 'none'; microphone 'none';");    
  21.      }    
  22.     await next();    
  23. });     
ConfigureServices method in Startup.cs
  1. services.AddHsts(options =>    
  2.  {    
  3.     options.IncludeSubDomains = true;    
  4.     options.MaxAge = TimeSpan.FromDays(365);    
  5. });     
web.config
  1. <configuration>    
  2.   <system.webServer>    
  3.     <security>    
  4.       <requestFiltering removeServerHeader="true" />    
  5.     </security>    
  6.     <httpProtocol>    
  7.       <customHeaders>    
  8.         <remove name="X-Powered-By"/>    
  9.       </customHeaders>    
  10.     </httpProtocol>    
  11.   </system.webServer>    
  12. </configuration>     
Let’s run the application and check those response headers on localhost. Here, I run the application on Google Chrome and verify those headers on network tab. You can use Fiddler as well to those headers. Awesome! All the headers are displaying as expected.
 
 
Now I deployed my website and re-scanned at https://securityheaders.com/
 
 
Excellent! We have implemented a secure web application using Http response headers in .NET core. This implementation could be suitable for WebAPI project as well. But it depends on your requirement.
 
I hope you gained some insights into this topic!
 
Last but not the least, these are some of the other useful resources which may help you to explore further:
  • https://docs.nwebsec.com/en/4.1/nwebsec/getting-started.html
  • https://owasp.org/www-project-top-ten/
  • https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers