ASP.NET MVC 5 Boilerplate Project Would Like To Use NWebSec

Oct 21, 2014 at 5:11 PM
Edited Oct 22, 2014 at 5:26 PM
I'm working on a ASP.NET MVC 5 Boilerplate Project and am using your NuGet packages to make the site secure by default. I intend to do a series of blog posts on RehanSaeed.co.uk fairly soon with attributions to this project. I have a few questions:

1- If I am using the MVC Filters, is it safe to omit the additions to web.config completely?
2- Using NWebSec's Content-Security-Policy with Elmah seems to break it. When clicking on the 'details' button on the Elmah detail page returns a 404 Not Found. I had a very basic policy where I allowed everything and it still errored.
3- CSP violations can be logged but most times, an exception needs to be created. I use the code below, feel free to add it to your library or not.
[Serializable]
public class CspViolationException : Exception
{
    #region Constructors

    public CspViolationException(CspViolationReport cspViolationReport)
        : this(GetCspViolationReportString(cspViolationReport))
    {
    }

    public CspViolationException(CspViolationReport cspViolationReport, Exception inner)
        : this(GetCspViolationReportString(cspViolationReport), inner)
    {
    }

    public CspViolationException(string message)
        : base(message)
    {
    }

    public CspViolationException(string message, Exception inner)
        : base(message, inner)
    {
    }

    protected CspViolationException(
      System.Runtime.Serialization.SerializationInfo info,
      System.Runtime.Serialization.StreamingContext context)
        : base(info, context)
    {
    }

    #endregion

    #region Private Static Methods

    private static string GetCspViolationReportString(CspViolationReport cspViolationReport)
    {
        cspViolationReport.ToString();
        return string.Format(
            "UserAgent:<{0}>\r\nBlockedUri:<{1}>\r\nColumnNumber:<{2}>\r\nDocumentUri:<{3}>\r\nEffectiveDirective:<{4}>\r\nLineNumber:<{5}>\r\nOriginalPolicy:<{6}>\r\nReferrer:<{7}>\r\nScriptSample:<{8}>\r\nSourceFile:<{9}>\r\nStatusCode:<{10}>\r\nViolatedDirective:<{11}>", 
            cspViolationReport.UserAgent,
            cspViolationReport.Details.BlockedUri, 
            cspViolationReport.Details.ColumnNumber,
            cspViolationReport.Details.DocumentUri,
            cspViolationReport.Details.EffectiveDirective,
            cspViolationReport.Details.LineNumber,
            cspViolationReport.Details.OriginalPolicy,
            cspViolationReport.Details.Referrer,
            cspViolationReport.Details.ScriptSample,
            cspViolationReport.Details.SourceFile,
            cspViolationReport.Details.StatusCode, 
            cspViolationReport.Details.ViolatedDirective);
    }

    #endregion
}
4- I've tried my best to come up with a default set of code without being too draconian and blocking everything, which is quite easy with CSP and would be frustrating for users as most people have never heard of CSP. So here is some of my code below, I thought I might get some input:
Oct 21, 2014 at 5:13 PM
public static class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        AddSecurityFilters(filters);
        AddContentSecurityPolicyFilters(filters);
    }

    /// <summary>
        /// Several NWebsec Security Filters are added here. See 
        /// <see cref="http://www.dotnetnoob.com/2012/09/security-through-http-response-headers.html"/> 
        /// and <see cref="http://nwebsec.codeplex.com/"/> for more information.
        /// </summary>
    private static void AddSecurityFilters(GlobalFilterCollection filters)
        {
            // Cache-Control: no-cache, no-store, must-revalidate
            // Expires: -1
            // Pragma: no-cache
            // Specifies whether appropriate headers to prevent browser caching should be set in the HTTP response.
            // Do not apply this attribute here globally, use it sparingly to disable caching.
            // filters.Add(new SetNoCacheHttpHeadersAttribute());   

            // X-Robots-Tag - Adds the X-Robots-Tag HTTP header. Disable robots from any action or controller this attribute is applied to.
            // filters.Add(new XRobotsTagAttribute() { NoIndex = true, NoFollow = true }); 

            // X-Content-Type-Options - Adds the X-Content-Type-Options HTTP header. Stop IE9 and below from sniffing files and overriding the Content-Type header (MIME type).
            filters.Add(new XContentTypeOptionsAttribute());

            // X-Download-Options - Adds the X-Download-Options HTTP header. When users save the page, stops them from opening it and forces a save and manual open.
            filters.Add(new XDownloadOptionsAttribute());

            // X-Frame-Options - Adds the X-Frame-Options HTTP header. Stop clickjacking by stopping the page from opening in an iframe or only allowing it from the same origin.  
            //      Deny - Specifies that the X-Frame-Options header should be set in the HTTP response, instructing the browser to display the page when it is loaded in an iframe - but only if the iframe is from the same origin as the page.
            //      SameOrigin - Specifies that the X-Frame-Options header should be set in the HTTP response, instructing the browser to not display the page when it is loaded in an iframe.
            //      Disabled - Specifies that the X-Frame-Options header should not be set in the HTTP response.
            filters.Add(
                new XFrameOptionsAttribute()
                {
                    Policy = XFrameOptionsPolicy.Deny
                });

            // X-XSS-Protection - Adds the X-XSS-Protection HTTP header. By default IE and Chrome try to detect XSS attacks. This will block the page if a XSS attack is detected.
            //      Note: There is a vulnerability in IE8 if this header is set which enables XSS to happen. See https://github.com/evilpacket/helmet#xss-filter-xssfilter and http://www.w3.org/TR/CSP.
            // filters.Add(new XXssProtectionAttribute() { BlockMode = true });
        }

    /// <summary>
        /// Adds the Content-Security-Policy (CSP) and/or Content-Security-Policy-Report-Only HTTP headers. 
        /// This creates a whitelist from where various content in a webpage can be loaded from. 
        /// Note: Not all browsers support this yet, use Firefox to test it but every little helps.
        /// You can read more at <see cref="http://www.dotnetnoob.com/2012/09/security-through-http-response-headers.html"/> 
        /// and 
        /// <see cref="http://nwebsec.codeplex.com/wikipage?title=Configuring%20Content%20Security%20Policy&referringTitle=NWebsec.Mvc"/>.
        /// </summary>
        /// <param name="filters"></param>
    private static void AddContentSecurityPolicyFilters(GlobalFilterCollection filters)
    {
        // Content-Security-Policy - Add the Content-Security-Policy HTTP header to enable Content-Security-Policy.
        filters.Add(new CspAttribute());
        // OR
        // Content-Security-Policy-Report-Only Add the Content-Security-Policy-Report-Only HTTP header to enable logging of violations withougt blocking them.
        // TO make use of this attribute, rename all the attributes below to their ReportOnlyAttribute versions e.g. CspDefaultSrcAttribute becomes CspDefaultSrcReportOnlyAttribute.
        // filters.Add(new CspReportOnlyAttribute());


        // Enables logging of CSP violations. See the NWebsecHttpHeaderSecurityModule_CspViolationReported method in Global.asax.cs to see where they are logged.
        filters.Add(new CspReportUriAttribute() { EnableBuiltinHandler = true });
        

        // The default-src directive sets a default source list for a number of directives. If the other directives below are not used then this is the default.
        filters.Add(
            new CspDefaultSrcAttribute()
            {
                // By default allow everything from the same domain.
                Self = Source.Enable
            });

        // connect-src - The connect-src directive restricts which URIs the protected resource can load using script interfaces (Ajax Calls and Web Sockets).
        filters.Add(
            new CspConnectSrcAttribute()
            {
                // Allow AJAX and Web Sockets to all resources.
                CustomSources = "*"
            });
        // font-src - The font-src directive restricts from where the protected resource can load fonts.
        // filters.Add(new CspFontSrcAttribute());
        // frame-src - The frame-src directive restricts from where the protected resource can embed frames.
        // filters.Add(new CspFrameSrcAttribute());
        // img-src - The img-src directive restricts from where the protected resource can load images.
        filters.Add(
            new CspImgSrcAttribute()
            {
                // Allow images from all sources.
                CustomSources = "*"
            });
        // script-src - The script-src directive restricts which scripts the protected resource can execute. The directive also controls other resources, such as XSLT style sheets, which can cause the user agent to execute script.
        filters.Add(
            new CspScriptSrcAttribute()
            {
                // Allow scripts from the CDN's.
                CustomSources = string.Format("{0}", ContentDeliveryNetwork.Google.Domain, ContentDeliveryNetwork.Microsoft.Domain),
                Self = Source.Enable
            });
        // media-src - The media-src directive restricts from where the protected resource can load video and audio.
        filters.Add(
            new CspMediaSrcAttribute()
            {
                // Allow audio and video from all sources.
                CustomSources = "*"
            });
        // object-src - The object-src directive restricts from where the protected resource can load plugins.
        filters.Add(
            new CspObjectSrcAttribute()
            {
                // Allow plugins from all sources.
                CustomSources = "*"
            });
        // style-src - The style-src directive restricts which styles the user applies to the protected resource.
        // filters.Add(new CspStyleSrcAttribute());
    }
}
Coordinator
Oct 21, 2014 at 10:56 PM
Edited Oct 21, 2014 at 10:56 PM
That sounds great! We need to raise the awareness of these security headers among devs, every blog post helps.

First post:
  1. As much as I'd like to answer yes, I have to say no. Historically (NWebsec 2.x), the headers were configured through web.config or the MVC attributes and the HttpModule would set the security headers accordingly. This behaviour changed with NWebsec 3.0 due to the deprecation of the PreSendRequestHeaders event in ASP.NET. Now, headers specified in web.config are set by the HttpModule, but they are also set/removed by the attributes and their config. Still, there are most likely a few dependencies on the module left. It might work, but I haven't tested that particular scenario. AFAIK all users of NWebsec are running the module. I think it's a good idea to support "attributes only", so I'll keep that in mind when I plan for the next releases. But I won't commit to that as a feature before I've included it in my tests and verified that it works as expected. If I do this change, I'll include it in the release notes and the documentation.
  2. I haven't tested it with ELMAH, have you checked the browser console or enabled CSP reports to see what's going on? I'd assume if there's something inherent in NWebsec that clashes with ELMAH, a user would have reported it sooner. But you never know. :)
  3. I'm not sure about the exceptions. Where I've used NWebsec, we've always just logged the reports. On a busy site, you'll probably get a high volume of reports. Throwing an exception and then logging the exception will be a huge performance hit compared to just logging the report. I've created the CSP report event to let users decide how they want to handle the reports, and I'd recommend they just log them.
Code post:

There are quite a few CSP attributes to play around with and the intention is that you can easily specify a baseline configuration either in web.config or when setting up the OWIN middleware, and then do the required overrides for a particular controller/action with the attributes. In my experience, that's what most users find is the easiest approach. There are two breeds of developers, those that prefer to do most of their configuration through config files, and those that prefer to set things up in code. Pre-OWIN I'd point the config file people to the web.config, and the config-by-code people to register the filter attributes as global filters. Now, I'd point the config-by-code folks to the OWIN middleware, as I believe that's easier.

Looking at the suggested configuration, I have a few comments.
  • One should be careful with the cache headers for public pages, as the browser will roundtrip the server for every page. For authenticated users, the cache headers are a must.
  • X-Robots-Tag will keep the pages out of search indexes, which is probably not what you want for public pages. For pages requiring authentication, search engines won't be able to index them anyway.
  • CSP - the power of CSP is that you can whitelist all the domains you trust to serve content in your page. Setting the source to * is best avoided, as it allows for injection attacks. So for a boilerplate MVC project I'd recommend either
Default-src 'self' // covers scripts and css (and all other content) from the site itself

or the slightly more paranoid

default-src 'none'; script-src 'self'; style-src 'self'

An example which includes CDNs is always useful, but it's worth noting that the application then fully trusts the CDN.

HTH, looking forward to the blog posts!
Oct 22, 2014 at 5:34 PM
Edited Oct 22, 2014 at 5:35 PM
  1. That's really great news. I run a couple of open source projects too, so I really appreciate the work that you have to put in.
  2. Ignore this, it was Firefox not playing nice with my companies proxy server.
  3. You are absolutely right, I only posted this code because the easiest way to custom log something to Elmah is to pass in an exception like so:
protected void NWebsecHttpHeaderSecurityModule_CspViolationReported(object sender, CspViolationReportEventArgs e)
{
        CspViolationException exception = new CspViolationException(e.ViolationReport);
        ErrorSignal.FromCurrentContext().Raise(exception);
}
  1. Regarding the first two bullet points, my code for those was actually commented out (CodePlex has no syntax highlighting so its hard to see). The intention being that I give a full description of what the filter can do and then its up to the user to apply it in their controller or action. I don't want the users to skip the filter just because they don't know its there.
With regards to CSP, I've gone with the more secure approach, although I am a little worried that users will be puzzled why their site is not working. Hopefully the Elmah errors will give them a clue.

I hope to post the project to GitHub (There are similar projects but none that are extensive and none that cover security in any depth) fairly soon. Once I've done that I'll do a blog post on each aspect of it.
Coordinator
Oct 22, 2014 at 10:09 PM
Ah, yes, I see that it's commented out. Maybe you could consider using https://example.com as an example instead of *? Devs might just uncomment the code and open the possibility for injection.

Yes, CSP is a bit tricky and can be a somewhat painful to get right when you're using a strict whitelist. On the other hand, it will lose much of its effect if the policy is too liberal. They'll have to work a bit for their CSP security.

Feel free to drop a link to the project in the comments here!
Oct 23, 2014 at 3:06 PM
There is always a big trade off between security and ease of development. I suspect that most security vulnerabilities are simply because devs got to lazy and frustrated when they couldn't get things working, so they just turn it off. CSP is easily one of those things that gets turned off without proper explanation and guidance.

Not sure when I'll have the project semi-finished and ready to blog about. There is a lot of work that needs to go into a default MVC project to make it secure, SEO friendly, fast, HTML5 compliant, browser compatible, error resistant, logs errors, lightweight, easy to use, support all iPhone/windows 8/android icons and uses industry best practices. I'm fairly close.
Oct 23, 2014 at 4:05 PM
I discovered an interesting problem with CSP and MVC's CDN bundling of JavaScript. As soon as you provide a CDN for your bundle, it adds a bit of inline JavaScript onto your page which loads the script locally if the CDN is not available. This is totally not allowed if you are using CSP, so you get CSP violation errors.

I have three solutions:
  1. Turn off the CDN backup script. So that if a CDN goes down, your site goes down with it. I'm not sure what the availability of Google's and Microsoft's CDN's are but I would have thought they would be pretty good.
  2. Move these inline scripts into their own JavaScript files. You can combine some of them into one file if you are including them together but things like modernizr need to be on their own. However, the whole point of using a CDN is for performance, so adding extra file requests feels wrong.
  3. Allow Unsafe Inline Scripts in CSP which is not great.
I'm not entirely sure which to choose.
Oct 23, 2014 at 4:25 PM
Edited Oct 24, 2014 at 10:42 AM
Furthermore, modernizr sets off further CSP violations. I've posted a question on StackOverflow here.
Coordinator
Oct 25, 2014 at 6:38 PM
About the CDN,
  1. I assume the CDN's have pretty good uptime, but you could point this out so people can make their own educated decision.
  2. This approach is the safest. The script will only be loaded once, as it's cached. So I wouldn't be to concerned. It shouldn't cause any notable delay in the normal case, when things are loaded from the CDN ok.
  3. No, that would reduce the security effect of CSP considerably. You could use the script nonce support that was added in the latest NWebsec. Note that the nonces would only work with the latest versions of Chrome and Firefox, CSP 1.0 UA's won't execute the script. So moving it to a separate script might be the best approach.
Yes, we've run into the Modernizr problem. We (at work) ended up generating our own Modernizr from their website, as we weren't using the features that caused the CSP violations.
Oct 27, 2014 at 3:14 PM
Yes, I think option 2 with liberal explanation is the best approach.

My StackOverflow and GitHub posts don't seem to have gathered much attention. There was one user however, that posted a link to an AngularJS page which shows that AngularJS have a CSP compliant mode which is 30% slower and requires an additional CSS file to be added.

The most important bits to start with are probably the HTML5Shiv for older browsers. That would probably at least get us going.
Nov 14, 2014 at 3:22 PM
ASP.NET MVC Boilerplate is now live on GitHub. For more information see my blog at http://rehansaeed.co.uk/asp-net-mvc-boilerplate.

Over the coming weeks, I'm going to create more blog posts and I think I'll create two covering CSP and the other filters provided by NWebSec. Thanks for all the help.
Coordinator
Nov 16, 2014 at 8:39 PM
Thanks for the ping! Looks like a great starting point for those that want to optimize and secure their web applications, I'll keep an eye out for the upcoming blog posts!
Coordinator
Feb 15, 2015 at 2:17 PM
Just to give you a heads up, NWebsec 4 is almost ready. It introduces a number of breaking changes for the MVC attributes. So if you're planning on demoing/blogging about it, it would be great if you based it on the new version.

Also, NWebsec has moved to Github! Give med a ping there if you have any further questions.
Feb 16, 2015 at 10:23 AM
That sounds great. I'll be sure to update ASP.NET MVC Boilerplate with your changes when its released. The ASP.NET MVC Boilerplate project has been in the wild for two months and already has close to 5,000 downloads.

I've been busy with preparing for a job interview so I haven't had a chance to write the post yet but will do so as soon as its over.
Coordinator
Feb 27, 2015 at 1:00 PM
The new version is out. I see that the boilerplate project has passed 6000 downloads, great work! I also see that there are sites popping up around the internet using your template.

It would be great if you also updated the link to NWebsec's project website.

Good luck on the job interview!
Feb 27, 2015 at 5:05 PM
I'm updating to 4.0, you should see it soon with the link also updated. I just noticed the session Security NuGet package. It sounds interesting but I think I need to read all your links and understand it before adding it.

Its number 6 on the most popular Visual Studio templates in the Gallery and its getting around 100 downloads a day.