This article is part of a series on the OWASP Top 10 for ASP.net Core. See below for links to other articles in the series.
|A1 – SQL Injection||A6 – Sensitive Data Exposure (Coming Soon)|
|A2 – Broken Authentication and Session Management||A7 – Insufficient Attack Protection (Coming Soon)|
|A3 – Cross-Site Scripting (XSS)||A8 – Cross-Site Request Forgery (Coming Soon)|
|A4 – Broken Access Control||A9 – Using Components with Known Vulnerabilities (Coming Soon)|
|A5 – Security Misconfiguration (Coming Soon)||A10 – Underprotected APIs (Coming Soon)|
In our previous article on the OWASP Top 10 we talked about SQL Injection. Where SQL Injection has a pretty definitive explanation and examples, this next one on “Broken Authentication and Session Management” is a bit more open ended. It covers everything from bad password storage systems (Plain text, weak hashing) to exposing of a session to a user that can then be stolen (For example a session string in a URL) all the way to simple things such as timing out a authenticated session.
As always, while the topics we talk about here are a good start to protecting your ASP.net Core application, they are by no means the end of the road. Especially when it comes to things like hashing of passwords. It’s a game of always staying up to date with the latest threats and updating your application as you go along.
Let’s jump right in!
It goes without saying that all passwords stored in a database should be hashed and have an individual salt (More on the individual salt later). Under no circumstances should passwords be stored as plain text. When you store plain text passwords, not only do you run the risk of if you get hacked, users on your website having their account stolen, but because people tend to use the same password on multiple sites, you run the risk of then being the source of pain for a user across every website they use.
Using ASP.net Core Identity
If you use ASP.net Core Identity framework, out of the box you are going to have secure password hashes and an individual salt used. Identity uses the PBKDF2 hashing function for passwords, and they generate a random salt per user. Ideally if you are unsure on what you are doing, use the out of the box functionality! Microsoft actually have pretty good documentation on getting the framework up and running.
Rolling Your Own
While using the out of the box ASP.net Core Identity framework is definitely preferable, there may be times where you just need to roll your own. But the roll your own only extends to the C# code that you are using to authenticate, under no circumstances should you “invent” a hashing algorithm of your own to store passwords.
OWASP recommends 4 different one way hashing functions for storing passwords. In order they are Argon2, PBKDF2, scrypt and bcrypt. If you intend to write your own authentication layer you must use one of these.
What Is Salting?
Salting is the act of adding a random string of text to your password before hashing it. In this way, even if the same password is hashed, the resulting hash will be different… Confused? Let’s use an example. I will use the PBKDF2 hashing function for this example.
Let’s say, I am using the password apples4tea . When I hash this I get the result of : 09ADB1C51A54C33C11CD3AE113D820305EFA53A173C2FF4A3150B5EC934E76FF . Now if a second user signs up to my site and uses the same password, they will have exactly the same hash. You can test this yourself by using a PBKDF2 Calculator online calculator here. Why is this bad? Well it means any hacker can essentially “pre-compute” what a password hash would be (For example, taking a list of the most popular passwords today), and simply do a string compare in a database.
Ontop of this, it means that all users who share the same password have the same hash. When one users password is “cracked” or even guessed, anyone else who uses that same password now has their password leaked also.
Now let’s say I add in a random salt for the user and I concatenate it onto the start of the password. The first time round my salt is H786Bnh54A . Hashing the full string of H786Bnh54Aapples4tea gives me the hash of DfsjpycJwtWkOu8UcP8YXC/G09HES8LU+kku0iSllO4= . Another user signs up to the site and a randomly generated hash is given to them of 76HNhg67Ac . Now we hash the salt with the same password and we end up with a hash of RP62+SmFCJLeQzROTtk5HpMId0zuFtsPeBFuBLpH/Sc=. Now we have the same password, and yet have a different hash.
Quite famously, Adobe had a giant data breach in 2013 that leaked users passwords. The passwords were not per user salted, and worst of all the password “hints” were stored along side the passwords. Meaning if a hint helped you guess one users password, any user using the exact same password would have the same hash in the database! Sophos did a great write up of the breach here. And of course there is an XKCD of the incident too.
If you use ASP.net Identity, the salt is stored with the password in the same column in the database (Because the salt is the same length everytime, we just count the characters as the salt, with the remainder being the hash).In previous iterations of ASP Membership (in .NET full framework), the salt was stored in a separate column. Either are fine as long as the salt is per user, and not application wide.
Exposing Session Identifiers
In previous versions of ASP.net, you could have a “cookieless” session. You would then see URL’s that looked not too dissimilar from the following : http://www.example.com/(S(lit3py55t21z5v55vlm25s55))/orderform.aspx . Where the session data is actually contained within the URL rather than in a cookie. This caused havoc for multiple reasons. Imagine sending this URL to a friend, and now they have access to your session data! Even worse is that if you click on any outbound links on this page, the referrer header will be set to include your session.
In ASP.net Core, cookieless sessions were actually never implemented so you won’t see URL’s like this. It doesn’t mean you won’t see people going ahead and trying to implement something similar in a roll-your-own type fashion however. In simple terms, no URL that is given to another user via any means should be able to suddenly impersonate the user.
Sending Data Over Unencrypted Connections
There is actually an entire OWASP Top 10 article coming up about encrypted connections, but it deserves special mention here. Even if your passwords are all hashed with a per user salt, and you don’t expose session identifiers in the URL, it means nothing if you are susceptible to “Man in the middle” attacks. A user connecting to some dodgy airport open wifi could mean they unintentionally broadcast our their clear text password when logging in.
SSL means you are encrypted from the a users computer to the server, no matter the route it takes – dodgy wifi included. Given free SSL providers such as Let’s Encrypt popping up, and even sites like Cloudflare offering free SSL. There is really no reason to not protect your users by taking advantage of SSL.
Lockouts, Timeouts, And More
On a final note, We’ve talked about ASP.net Core Identity so much in this article. But again I would like to point out just how much it enforces good habits. Let’s just take a look at the setup for Identity that you would typically put in your Configure Services method.
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.SignIn.RequireConfirmedEmail = true;
options.User.RequireUniqueEmail = true;
Look at the options you have here. There are very good password enforcement options (Although arguments can be made that you shouldn’t go overboard on requiring things like alphanumeric as people tend to then just write them down on pieces of paper…), good options for locking a users account if they continually type in bad passwords, and even requiring email confirmation (Which again, is part of the Identity framework).
And in terms of how the cookies are stored on a users machine, we have these options :
options.Cookie.HttpOnly = true;
options.Cookie.Expiration = TimeSpan.FromHours(1)
options.SlidingExpiration = true;
Again all great best practices. A users cookie should always have an expiration by default. An example from OWASP even explicitly states the “public computer” session timeout issue in examples of attacks :
I use these examples because they are great pointers to best practice when it comes to authentication and session management. Even if you decide to roll your own, you should investigate what Identity gives you out of the box so that you can replicate some or all of the functionality, and always keep up to date with the latest improvements to the framework.
Authentication and session management is such a broad topic and one that I think is plagued by slow moving legacy applications. Often when I’ve shown new developers things such as session identifiers in the URL, they can’t imagine why someone would ever do that. “Did people actually ever think that was secure?” is an actual quote from a junior developer I’ve worked with. I think that’s actually the essence of the topic, that we need to be on the forefront of what’s new and secure when it comes to authentication and sessions, so that we stay ahead of the “Did people actually every think that was secure?” curve.
In our next article, we will investigate the never ending fight against Cross-Site Scripting (Or XSS). See you then!