cloudscribe / cloudscribe.templates

dotnet new templates for cloudscribe
Apache License 2.0
5 stars 3 forks source link

It would be nice to include NWebsec in the project template #48

Closed joeaudette closed 5 years ago

joeaudette commented 5 years ago

Migrating this discussion from an email received from Mitch Howard.

Notes from Mitch Howard

Hi, Joe. Just a quick FWIW: When I've been deploying various Azure sites recently, I've been using NWebSec middleware

https://docs.nwebsec.com/en/latest/

https://www.dotnetnoob.com/2012/09/security-through-http-response-headers.html

to manage headers related to security such as: X-Frame-Options Strict-Transport-Security X-Content-Type-Options X-Download-Options X-XSS-Protection X-Content-Security-Policy / X-Content-Security-Policy-Report-Only Some headers are newer and still in beta, but others are well-established.

I've been using https://securityheaders.com/ to grade the sites after implementation. Since I know you to be very security-minded, I thought you might be interested in incorporating these into the stock Cloudscribe code.

For NWebSec, one only has to load the nuGet package and add a few lines of code in Configure().

If you prefer the Cloudscribe components to be entirely yours, rolling your own middleware to accomplish what you need isn't too complicated either.

For a test instance of Cloudscribe Core, I used this code:

if (env.IsDevelopment()) 
{ 
    app.UseDeveloperExceptionPage(); 
    app.UseDatabaseErrorPage(); 
} 
else
 { 
    app.UseExceptionHandler("/oops/error"); 
    // Commented out delivered 3 lines 
    //if (_sslIsAvailable) 
   { // app.UseHsts(); //} }
 if (_sslIsAvailable) 
 { 
    #region NWebsec Security Headers Middleware extensions 
     // See: https://docs.nwebsec.com/en/latest/nwebsec/Configuring-csp.html# 
     // and https://damienbod.com/2018/02/08/adding-http-headers-to-improve-security-in-an-asp-net-mvc-core-application/ 

     app.UseHsts(hsts => hsts.MaxAge(SecHdrOptions.HstsDays)); 
     app.UseRedirectValidation(options => options.AllowSameHostRedirectsToHttps()); 
     app.UseXContentTypeOptions(); 
     app.UseReferrerPolicy(opts => opts.NoReferrer()); 
     app.UseXXssProtection(options => options.EnabledWithBlockMode()); 
    app.UseXfo(xfo => xfo.SameOrigin()); 
    app.UseCsp(opts => opts 
    .BlockAllMixedContent() 
     .StyleSources(s => s.Self()
           .UnsafeInline()
        .CustomSources(SecHdrOptions.WhitelistStyles)) 
   .FontSources(s => s.Self()
          .CustomSources(SecHdrOptions.WhitelistFonts)) 
   .FormActions(s => s.Self()) 
   .FrameAncestors(s => s.Self()) 
   .ImageSources(s => s.Self().CustomSources(SecHdrOptions.WhitelistImages)) 
   .ScriptSources(s => s.Self()
        .UnsafeInline()
        .UnsafeEval()
        .CustomSources(SecHdrOptions.WhitelistScripts)) ); 
 #endregion 

and this static class:

public static class SecHdrOptions 
{ 
    public const int HstsDays = 180; 
    // Whitelists for NWebsec Middleware 
   public static readonly string[] WhitelistScripts = new string[] 
   { 
         "https://google.com", 
         "https://ajax.googleapis.com", 
          "https://www.google-analytics.com", 
          "https://www.gstatic.com", 
          "https://cdnjs.cloudflare.com", 
           "https://www.google.com/recaptcha/" 
   }; 

    public static readonly string[] WhitelistImages = new string[] 
    { 
         "https://cloudscribe.com", 
         "https://google.com", 
         "https://ajax.googleapis.com",
         "https://www.google-analytics.com",
         "data:", 
        "https://www.gstatic.com", 
        "https://cdnjs.cloudflare.com",
         "https://secure.gravatar.com"
     }; 

    public static readonly string[] WhitelistFonts = new string[] 
    { 
         "https://fonts.googleapis.com",
          "https://fonts.gstatic.com" 
     }; 

    public static readonly string[] WhitelistStyles = new string[] 
    { 
        "https://google.com", 
        "https://fonts.googleapis.com", 
        "https://ajax.googleapis.com",
         "https://www.gstatic.com",
         "https://cdnjs.cloudflare.com" }; 
  } 

The whitelists could be kept in config files or the database. I hope this was helpful.

joeaudette commented 5 years ago

I think that it is worth considering whether to incorporate NWebsec as an option or by default in our project template. I think it needs some careful thought and review though to make sure we don't break any cloudscribe features and we don't degrade the out of the box experience for new projects with cloudscribe.

Also I want to try to avoid people thinking that we've secured their app and they don't have to do anything or understand it.

Some things that will need review:

joeaudette commented 5 years ago

I've been doing some study on CSP and use of NWebSec and have played with it in the source solution for cloudscribe Core. Specifically in that solution, at the moment I have it setup like this:

app.UseRedirectValidation(options => options.AllowSameHostRedirectsToHttps());
        app.UseXContentTypeOptions();
        app.UseReferrerPolicy(opts => opts.NoReferrer());
        app.UseXXssProtection(options => options.EnabledWithBlockMode());
        app.UseXfo(xfo => xfo.SameOrigin());
        app.UseCsp(opts => opts
            .BlockAllMixedContent()
            .StyleSources(s => s.Self()
               .UnsafeInline()
            // .CustomSources(cspConfig.WhitelistStyles.ToArray()) //throws an error if empty array
            )
            .FontSources(s => s.Self()
              // .CustomSources(cspConfig.WhitelistFonts.ToArray())   //throws an error if empty array
              )
             .FormActions(s => s.Self())
             .FrameAncestors(s => s.Self())
             .ImageSources(s => s.Self()
                 .CustomSources(cspConfig.WhitelistImages.ToArray())
              )
             .ScriptSources(s => s.Self()
                .UnsafeInline() //ckeditor toolbars buttons don't work without this unfortunately
                .UnsafeEval() //without this jquery.unobtrusiveajax doesn't work
               .CustomSources(cspConfig.WhitelistScripts.ToArray())  //throws an error if empty array
              )
            );

I have also introduced a model class for configuration so for example in that solution I have this in appsettings.json:

"ContentSecurityPolicyConfiguration": {
    "HstsDays": 180,
    "WhitelistScripts": [
      "https://www.google.com/recaptcha/"
     //these are ckeditor inline scripts
     //"sha256-y8DInSr2zF7PN5eoUJaOub06SWAs7LS0I9qvOBzB24w=",
     //"sha256-kCHLgxFYfRBgcPvUY36pivVG5Yzj/sXVNua5iRd7Cog=",
      //"sha256-jXsJOuxldB0vgf1I6X5N+ebOXi/v0v61nCxWZeyw1t8="
    ],
    "WhitelistStyles": [

    ],
    "WhitelistImages": [
      "https://secure.gravatar.com"
    ],
    "WhitelistFonts": [

    ]
  },

which can be wired up like this:

 services.Configure<ContentSecurityPolicyConfiguration>(_configuration.GetSection("ContentSecurityPolicyConfiguration"));

The real holy grail for CSP is to not use .UnsafeInline() because if you prevent use of inline script you really block most xss. So I initially left that out to make it really strict and then see what breaks. Then I went through cloudscribe core pages looking for broken things and searched the code for any inline scripts. I did quite a lot to eliminate inline scripts in cloudscribe core and simple content hoping that we could get everything working without .UnsafeInline(). But ultimately I found that CKeditor just cannot work without .UnsafeInline(). So even though I did clean up other places in cloudscribe to use only external scripts it still requires .UnsafeInline() for pages that use ckeditor.

As Troy Hunt explains in this tutorial CSP is designed to break things. Unfortunately there is no one size fits all solution for CSP. As such it seems risky to me for us to add NWebSec into our project template and try to prescribe specific use of CSP. I plan to use it in my own projects for a while and then maybe will revisit the idea. But Microsoft doesn't include NWebSec in their project templates nor do they add CSP by default, probably because it is hard to get that right in a way that won't break apps.

My main goal for the project template is to provide the code that wires up our nuget packages with the features users choose. It is a lot of work just maintaining and supporting the nugets, I don't want to own other people's apps or be on the hook for their application security. While I think it could be nice to have an opinionated project template that tries to setup the strongest possible security that is a tough goal and can be misleading to people who won't follow up and understand what the CSP code is doing and then it will just reflect badly on cloudscribe if our default application code breaks features they build on top of cloudscribe.

So for now I'm going to close this issue and not plan on adding NWebSec into our project template, but I am going to begin using it as much as possible in my own projects, and at some point maybe could at least publish a document with some recommendations. For now I would refer those interested to the Troy Hunt articles and the NWebSec documentation.

joeaudette commented 5 years ago

It looks like maybe there will be something in the aspnetcore framework for CSP in the 3.1 release. https://github.com/aspnet/AspNetCore/issues/6001

It would be much nicer to be able to adjust CSP headers per request instead of just once at startup to accommodate urls where we need to be more lax such as where using ckeditor.

I noticed NWebsec project has not been updated in over a year and their website ssl cert expired in Feb 2019 https://www.nwebsec.com/ which makes it seem like an abandoned project.

joeaudette commented 5 years ago

Just some further thoughts on this. While NWebSec was good in its day, I think the idea of one global set of of CSP headers registered at application startup is bad. If you add a bunch of allowed CDNs for example the header becomes larger than it needs to be on all pages even if the page is not using anything from those CDNs. It is much better to have CSP rules live close to the url endpoint so that you can have different CSP headers for different urls only including the resources actually used on the page. Then it would be easy to allow unsafe-inline for example on edit pages (to accommodate ckeditor) without allowing it everywhere as it would be if you use that globally.

The discussion in the aspnet core repo sounds like they plan to use razor directives so each page can declare its own needed scripts and resources to be reflected in the CSP header not some global set used for every url. That will be much better.

joeaudette commented 5 years ago

just a note that there is a new more modern kit for content security headers in asp.net core

https://github.com/andrewlock/NetEscapades.AspNetCore.SecurityHeaders

but it still seems like a centralized things done at app startup not per request which is what would be better. Still waiting to see what Microsoft provides in the future.