Scott Hanselman

Easily adding Security Headers to your ASP.NET Core web app and getting an A grade

March 24, '20 Comments [9] Posted in ASP.NET | Linux
Sponsored By

Well that sucks.

Score of F on SecurityHeaders.com

That's my podcast website with an F rating from SecurityHeaders.com. What's the deal? I took care of this months ago!

Turns out, recently I moved from Windows to Linux on Azure.

If I am using IIS on Windows, I can (and did) make a section in my web.config that looks something like this.

Do note that I've added a few custom things and you'll want to make sure you DON'T just copy paste this. Make yours, yours.

Note that I've whitelisted a bunch of domains to make sure my site works. Also note that I have a number of "unsafe-inlines" that are not idea.

<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Strict-Transport-Security" value="max-age=31536000"/>
<add name="X-Content-Type-Options" value="nosniff"/>
<add name="X-Xss-Protection" value="1; mode=block"/>
<add name="X-Frame-Options" value="SAMEORIGIN"/>
<add name="Content-Security-Policy" value="default-src https:; img-src * 'self' data: https:; style-src 'self' 'unsafe-inline' www.google.com platform.twitter.com cdn.syndication.twimg.com fonts.googleapis.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' www.google.com cse.google.com cdn.syndication.twimg.com platform.twitter.com platform.instagram.com www.instagram.com cdn1.developermedia.com cdn2.developermedia.com apis.google.com www.googletagservices.com adservice.google.com securepubads.g.doubleclick.net ajax.aspnetcdn.com ssl.google-analytics.com az416426.vo.msecnd.net/;"/>
<add name="Referrer-Policy" value="no-referrer-when-downgrade"/>
<add name="Feature-Policy" value="geolocation 'none';midi 'none';notifications 'none';push 'none';sync-xhr 'none';microphone 'none';camera 'none';magnetometer 'none';gyroscope 'none';speaker 'self';vibrate 'none';fullscreen 'self';payment 'none';"/>
<remove name="X-Powered-By" />
<remove name="X-AspNet-Version" />
<remove name="Server" />
</customHeaders>
</httpProtocol>
...

But, if I'm NOT using IIS - meaning I'm running my ASP.NET app in a container or on Linux - this will be ignored. Since I recently moved to Linux, I assumed (my bad for no tests here) that it would just work.

My site is hosted on Azure App Service for Linux, so I want these headers to be output the same way. There are several great choices in the form of Open Source NuGet libraries to help. If I use the ASP.NET Core middleware pipeline then these headers will be output and work the SAME on both Windows AND Linux.

I'll be using the NWebsec Security Libraries for ASP.NET Core. They offer a simple fluent way to add the headers I want.

TO BE CLEAR: Yes I, or you, can add these headers manually with AddHeader but these simple libraries ensure that our commas and semicolons are correct. They also offer a strongly typed middleware that is fast and easy to use.

Taking the same web.config above and translating it to Startup.cs's Configure Pipeline with NWebSec looks like this:

app.UseHsts(options => options.MaxAge(days: 30));
app.UseXContentTypeOptions();
app.UseXXssProtection(options => options.EnabledWithBlockMode());
app.UseXfo(options => options.SameOrigin());
app.UseReferrerPolicy(opts => opts.NoReferrerWhenDowngrade());

app.UseCsp(options => options
.DefaultSources(s => s.Self()
.CustomSources("data:")
.CustomSources("https:"))
.StyleSources(s => s.Self()
.CustomSources("www.google.com","platform.twitter.com","cdn.syndication.twimg.com","fonts.googleapis.com")
.UnsafeInline()
)
.ScriptSources(s => s.Self()
.CustomSources("www.google.com","cse.google.com","cdn.syndication.twimg.com","platform.twitter.com" ... )
.UnsafeInline()
.UnsafeEval()
)
);

There is one experimental HTTP header that NWebSec doesn't support (yet) called Feature-Policy. It's a way that your website can declare at the server-side "my site doesn't allow use of the webcam." That would prevent a bad guy from injecting local script that uses the webcam, or some other client-side feature.

I'll do it manually both to make the point that I can, but also that you aren't limited by your security library of choice.

NOTE: Another great security library is Andrew Lock's NetEscapades that includes Feature-Policy as well as some other great features.

Here's my single Middleware that just adds the Feature-Policy header to all responses.

//Feature-Policy
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Feature-Policy", "geolocation 'none';midi 'none';notifications 'none';push 'none';sync-xhr 'none';microphone 'none';camera 'none';magnetometer 'none';gyroscope 'none';speaker 'self';vibrate 'none';fullscreen 'self';payment 'none';");
await next.Invoke();
});

Now I'll commit, build, and deploy (all automatic for me using Azure DevOps) and scan the site again:

Score of A on SecurityHeaders.com

That was pretty straightforward and took less than an hour. Your mileage may vary but that's the general idea!


Sponsor: Protect your apps from reverse engineering and tampering with PreEmptive, makers of Dotfuscator. Dotfuscator has been in-the-box with Microsoft Visual Studio since 2003. Mention HANSELMAN for savings on a professional license!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb
Tuesday, March 24, 2020 10:25:32 AM UTC
Unsafe eval and unsafe inline might not be the best options for a CSP Policy. Might be worth linking somewhere so people can see what all the values mean rather than copy pasting what you have here?
Drew Freyling
Tuesday, March 24, 2020 12:13:05 PM UTC
I scanned a few websites, some big (Microsoft, Google, Gmail, Bank of America, Hotmail.com, Yahoo.com, Facebook).

Only facebook, yahoo and gmail got As. The rest got Ds.

I thought that was interesting. I scanned yours as well (this site) and yours is just as or actually more secure now than theirs.
Mike Cheel
Tuesday, March 24, 2020 1:38:35 PM UTC
When you add such constraints as part of your HTTP headers or in the META tags in the web page itself, you are basically telling people how exactly they can craft malicious attacks using your websites.

For example, now that someone knows what domains you trust for scripts, an attacker could use a DNS spoofing/poisoning + man-in-the-middle with malicious scripts that appear to be from that domain and use it to do really bad things to your visitors.

Because of this, adding such headers is not really secure. If anything, it is "anti-secure".

Comments?
Tuesday, March 24, 2020 1:47:16 PM UTC
And for those of you on .NET full fat you're sod out of luck and have to completely roll your own. All the nuGet packages relating to this are .NET core now (if they ever supported full fat .NET) and those that aren't haven been updated in years and are not very popular which leads me to have little trust in them.

We do have our own IIS HttpSecurityHeader module so it's built into our site as opposed to relying on IIS.

[SCOTT] Question for you. Can you find out why IIS insists on adding headers by default to websites that will cause a failure in penetration tests? Such as powered by IIS and the like? Worse than that these can't be overridden in the above module, :/
Peter
Tuesday, March 24, 2020 2:56:31 PM UTC
@Sujay Sarma - I respectfully disagree. You are correct that adding these headers tells an attacker which scripts on an external site to attack in order to get malicious code onto your site. But that doesn't mean it's a bad idea, particularly as part of a defense in depth strategy. (Even as a standalone, simply parsing the page source would yield the same information.)

There's another tool, subresource integrity (SRI), in which you add an "integrity" attribute containing a checksum to any script tag which references an external site. That way if the external script is modified in any way, the checksum won't match and the browser can block it. According to Mozilla's SRI documentation it's supported everywhere except Edge, IE (no longer, IMHO, a modern browser anyhow) and Opera mobile.

I couldn't find where Scott has written anything about this yet, but Scott Helme (founder of SecurityHeaders.com) has a nice write-up of SRI on his blog. (Combine that with the aforementioned Mozilla documentation and you can do this manually pretty easily.)
Tuesday, March 24, 2020 3:53:20 PM UTC
I checked your site on https://headerinspector.com, and you have a great score there too... But there's a few other items worth checking into, like fully configuring HSTS, and some legacy browser headers
Tuesday, March 24, 2020 4:39:44 PM UTC
@Sujay Sarma: These headers are not designed to address MITM attacks. These defend against script injections and XSS attacks. Public Key Pinning could be used against MITM attacks, but that's been deprecated.

@That Blair Guy: SRI is pretty awesome. There used to be a require-sri-for directive in CSP, but that's been deprecated, too.
Thursday, March 26, 2020 9:22:59 PM UTC
Peter - Um, no you don't, I literally included the IIS settings right there. I had a A grade with full framework and IIS.
Scott Hanselman
Saturday, March 28, 2020 9:24:09 AM UTC
I use nginx as reverse proxy and it automatically sets security headers, when used with 'Lets encrypt' Certbot.
Name
E-mail
(will show your gravatar icon)
Home page
2+5

Comment (Some html is allowed: a@href@title, b, blockquote@cite, em, i, li, ol, pre, strike, strong, sub, super, u, ul) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Live Comment Preview

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.