Hardening the security of your Asp.Net Core apps


I recently had to go through a third party penetration testing process. This is something you will eventually have to do as part of you development lifecycle if you have a customer who really care about security or if you just want to sleep at night.

I think there are a couple of things which came out that can be caught earlier in the development, hence save some time. In this post, I will share the main items and solutions with you.

User input

Cases

User input is an obvious one and you might be wondering how it would have ended up here. I'm talking about the most common and simple use case, you have a set a of fields such as title and description. A user with bad intentions can put scripts in there and make your page run it. I will leave vulnerabilities such as the most trivial scenarios of XSS.

But, it's trivial and everyone knows about it right? And every modern framework handles it, RIGHT?

Well, it might be true today and whether you take Angular or Razor, they have good default behavior, which tends to drive the developer to do the right thing. But it wasn't always the case.

A good old JQuery Component

In our application, we have many places where we allow user input and don't restrict it to only alphanumeric characters. It would be a terrible user experience thing to do (I'll come back to it later). However we found issues only in two places. The first culprit was our treeview  component.

We are using Fancytree, which is a popular open-source tree component. It has been developed under the JQuery golden era and, as such, it does not escape the content it displays by default. So mark my words: it is unsecure by default. Worse, the library author didn't see it as a problem.

I'm not trying to shame him here, we all have righteous moments like that. In those we make bold, virtuous statements like "the developer should always do this or that". But, if you want your mindset to switch towards security first, you can't allow yourself to think this way. Furthermore, as a library author, you should think security first. It is only two years later, under other peer pressure, that he finally added an option to turn escaping on. It remains unsecured by default.

We wanted a wysiwyg html editor

Our PM wanted users to be able to format the descriptions in a friendly fashion. The most common thing to do is to throw an html editor in there and store the html code. Well, now, nothing prevents the user to type malicious code in.

In that case, we are using the pretty popular and quite good Summernote. You would expect such a popular project to have tackled the security heads, but no.

Solutions

Validation

One might argue we should not allow < > or html tags in user input. The problem with that kind of excessively strict validation is that you prevent the user to type what he really wants. And sometimes there are legitimate reasons to have tags in user input. Not that you want them to be executed, but to be displayed as text.

In addition, consider the following scenario: There's another way to save data which bypasses your validation (likely another security flaw). Validation will do nothing for you and the harmful code will be displayed.

So, yes, do validate user input, but don't rely exclusively on it.  Also, keep in mind that excessive restrictions will hurt usability.

Escaping

In my opinion,  this should always be done, wherever applicable and whether or not the data has been validated. It will be your last fence against executing harmful code. Whenever you display things, it should always be escaped, unless you are 100% sure the data is safe and you intend the html to be executed.

The good news is that Razor does that by default, and so does Angular. So it comes down to unsecured library usage. You should keep track of what's being used, where and why.  Before allowing a new dependency to come along, you should conduct your own diligence and make sure your usage of it will not bring security flaws in your system.

In addition, it might be a great idea to setup some dependency vulnerability monitoring solution and license compatibility. There are a couple of options on the market, both paid and open source, as library not following this principle tend to show up on CVE. On the free side, you might get some useful things from RetireJS. Although it will also give you some false positives. On the commercial side, the two main contenders are Blackduck and Whitesource.

White listing

When it comes to html content, escaping won't work. In that case you will have to opt for a completely different strategy.

What I would recommend consists into parsing the html content to leave only safe and useful elements. This is white listing.

The best thing is that, thanks to the excellent library HtmlSanitizer, it is dead simple:

var sanitizer = new HtmlSanitizer();

var html = @"<script>alert('xss')</script><div onload=""alert('xss')"""
    + @"style=""background-color: test"">Test<img src=""test.gif"""
    + @"style=""background-image: url(javascript:alert('xss')); margin: 10px""></div>";
 
var sanitized = sanitizer.Sanitize(html, "http://www.example.com");

Assert.That(sanitized, Is.EqualTo(@"<div style=""background-color: test"">"
    + @"Test<img style=""margin: 10px"" src=""http://www.example.com/test.gif""></div>"));

Malwares

Cases

It's quite common in applications to have to deal with file uploads. What happens if a user uploads a malware, either intentionally or not? You might think it's the role of the network to enforce strict malware protection on devices. However, what about personal devices (BYOD)? What if for some reason, the user AV are not running. What if those files are served to consumers?

You become the weak point of the company IT and your software becomes a prime candidate to bootstrap a wider attack.

Solutions

So, you should consider scanning files for malware. I'm not sure about what are the good commercial solutions when it comes to antiviruses with an API/SDK. If you know any good one, please leave a comment. However there's an Open Source solution.

It's called ClamAV. Keep in mind that this is GPL software. So you can't bundle it or statically link it to non GPL software. However, it is perfectly fine to deploy a cluster of ClamAV instances. There a couple of options. Docker is probably my favorite option. Note that if you are desperate for a Windows solution there's a Windows port too.

Now, all you need is to talk with ClamAV APIs. To do so, there's already a netstandard library readily available for you to consume: nClam.

using System;
using System.Linq;
using System.Threading.Tasks;
using nClam;

class Program
{
    static async Task Main(string[] args)
    {
        var clam = new ClamClient("localhost", 3310);
        var scanResult = await clam.ScanFileOnServerAsync("C:\\test.txt");  //any file you would like!

        switch (scanResult.Result)
        {
            case ClamScanResults.Clean:
                Console.WriteLine("The file is clean!");
                break;
            case ClamScanResults.VirusDetected:
                Console.WriteLine("Virus Found!");
                Console.WriteLine("Virus name: {0}", scanResult.InfectedFiles.First().VirusName);
                break;
            case ClamScanResults.Error:
                Console.WriteLine("Woah an error occured! Error: {0}", scanResult.RawResult);
                break;
        }
        
    }
}

Weak Authentication

Passwords and lockout

Yes, developers, testers and sales are all the same. They are lazy :). They hate those complicated password on test environments.

So, one day, someone decided to disable the restrictions on lockout and password. We'll re-enable it in production later they said! And here we are, allowing weak password in production.

Do not disable strong password or lockout by default. Make it a developer only setting if you really need that (otherwise people will misconfigure). But please, consider using a good password manager. My heart goes to 1Password, especially since their last update.

Two-factor authentication

Just do yourself a favor, enable two-factor authentication.  90% of the hard work is done by the framework and it supports both TOTP based authenticators and SMS.

Basically, all that's left for you to do is write some css.

Cookies

By default, identity cookie embed the authentication ticket and it comes with a 14 days validity. It means that even if a user logout, the cookie (if acquired by someone else) will still be valid for the remaining of the authentication ticket lifetime. You can easily validate that with Fiddler.

To handle that, Asp.Net core provides an extensibility point to store tickets on the server side and destroy it on logout. It is achieved by implementing and providing a ITicketStore to the SessionStore on you cookie configuration.

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options => {
        options.SessionStore = new MyDistributedSessionStore();
    });

Storing secrets

You might have to store some secrets or confidential data. For instance, if you are building a reporting solution, you might have connections string to various datasources. In that case, it is quite required that you don't store those information in cleartext. It might save you from very embarrassing disasters.

To help solving this issue, Asp.net core provides an extensible DataProtection API. It is quite flexible, with support for Redis to Azure Key Vaults. Just don't store the keys in the database :) and if on Azure, prefer the Key Vault. Also, you can easily make sure that only the right code can decipher the data, thanks to purpose strings.

So, what are you waiting for? Just use it!

Conclusion

In this article we've talked about a couple of points that might sound obvious. Especially when the solutions are so simple. However, I would be surprised if you don't witness one or more of these issues on new developments.

In addition to the points described in this post, I strongly suggest you spend some time reading and absorbing the security section of the official Asp.Net Core documentation.

Also, if you think of common issues and solutions please leave a comment!


Credits
Cover photo by Sandeep Swarnkar
Malwares Photo by Markus Spiske
User input by Javier Quesada
Authentication by James Pond
Cookies by Jennifer Pallian