Scott Smith

Blog Tutorials Projects Speaking RSS

Secure Node Apps Against OWASP Top 10 - Authentication & Sessions

Welcome to part 2 of the OWASP security series

  1. Injection
  2. Broken Authentication & Session Management
  3. Cross Site Scripting (XSS)
  4. Cross Site Request Forgery (CSRF)
  5. Using Components with Known Vulnerabilities (Coming soon)

In this multipart series, we will explore some of the the OWASP top web application security flaws including how they work and best practices to protect your application from them. The focus will be on Express web applications in Node, but the principles shown can be applied to any framework or environment.

This part will cover broken authentication and session management.

Broken Authentication and Session Management

So what exactly is a broken authentication or session management vulnerability? OWASP defines it as follows:

“Attacker uses leaks or flaws in the authentication or session management functions (e.g., exposed accounts, passwords, session IDs) to impersonate users.”

“An attacker can be an anonymous external attacker or a user with their own account who may attempt to steal accounts from others.”

“Such flaws may allow some or even all accounts to be attacked. Once successful, the attacker can do anything the victim could do. Privileged accounts are frequently targeted.”

The following scenarios will highlight some of the ways in which this type of vulnerability can be exploited and best practices to protect against them. Like all things security related, this list should not be considered the only ways in which you could be vulnerable.

Scenario 1: Plain text passwords

Surprisingly, there are still many sites that store user passwords in plain text. This becomes obvious when a site gets hacked and all their user’s information along with passwords are exposed.

This is bad for obvious reasons. Once an attacker knows a user’s password, they can now log in as that user and do whatever they want.

You should always build your applications assuming you will get hacked or data will get leaked. By taking this approach, you will be much more likely to take precautions to secure sensitive information. User passwords are one of the most important pieces of data to secure.

The best way to secure user passwords is with a strong hash. Encrypting a password is better than plain text but still not as secure as a hash. With encryption, all the attacker needs do is get a hold of the keys and they can determine the password.

One way to hash and verify passwords is with the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var bcrypt = require('bcrypt-nodejs');

var hashPassword = function(password, callback) {
  bcrypt.genSalt(10, function(err, salt) {
    if (err) return callback(err);

    bcrypt.hash(password, salt, null, function(err, hash) {
      if (err) return callback(err);

      callback(null, hash);
    });
  });
};

var verifyPassword = function(password, hash, callback) {
  bcrypt.compare(password, hash, function(err, isMatch) {
    if (err) return callback(err);

    callback(null, isMatch);
  });
};

Just because your user’s passwords are hashed doesn’t mean you shouldn’t worry about locking down your application. Even with hashed passwords, an attacker can eventually determine the passwords of some of your user’s accounts with enough time.

Scenario 2: Session IDs in the URL

Session IDs are unique numbers web applications assign to a user for the duration of the user’s visit. It is used so the web server knows the identify of the user making the request without the user having to log in again.

Because the session ID can be used to identify a user as well as grant permissions, protecting it is very important. By passing the session ID via the URL, you are exposing your application and users to a hacker. Imagine the following request being made to your bank:

https://bank.com/account?sessionid=1234567

Passing session IDs like this can be bad for the following reasons:

  • Sharing of link grants others full access
  • Stored on cache servers
  • Stored in browser history
  • Leaked through the Referer header
  • Leaked through logs not properly protected
  • Much more visible and thus more dangerous

The solution to this problem is pretty simple. Pass session IDs via cookies and not the URL. The following code is an example Express application that uses cookies for the session ID.

1
2
3
4
5
6
7
8
9
10
11
var express = require('express');
var session = require('express-session');

var app = express();

app.use(session({
  secret: 'our super secret session secret',
  cookie: { maxAge: 3600000 } // 2 hours in milliseconds
}));

app.listen(80);

Scenario 3: Site accessed over HTTP

The next scenario where vulnerabilities can occur is if your site is accessed over HTTP.

This can be bad if you are not taking the necessary precautions to protect your users. Imagine if your user is on a public unencrypted wifi network. If your site is over HTTP, it is very easy for someone else on that wifi network to monitor and obtain your session ids. Even if the user is on a secure network, there is no guarantee that a third party somewhere along the route to the web application is not monitoring traffic.

Solution 1

The first and most obvious solution here is to run your site over HTTPS. If you have user accounts, users logging in, or session sate you should not run your site over HTTP. It is very affordable and even free solutions exist. By running over HTTPS, you will reduce a significant attack vector within your application.

Solution 2

The second solution is to not allow cookies to be sent over HTTP. Even if your site runs over HTTPS, you will very likely still be listening for HTTP requests in order to redirect them to the HTTPS endpoint. Because of this, unless you take measures, a user can still send their session id via the cookie over an unsecured channel.

In order to stop this, you need to use the secure flag for your cookies. This flag, when set in the response that sets the cookie, will tell the browser to only send this cookie over an HTTPS request.

The following Express application from previous examples is updated to show how to do this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var express = require('express');
var session = require('express-session');

var app = express();

app.set('trust proxy', 1);

app.use(session({
  secret: 'our super secret session secret',
  cookie: {
    maxAge: 3600000,
    secure: true
  }
}));

app.listen(80);

Here is an example of what the header looks like with both the secure and httpOnly flags set.

1
Set-Cookie: connect.sid=fjdskfjdsksdfjksa; Domain=favatron.com; Path=/; Expires=Sun, 28 Jun 2015 02:12:41 GMT; Secure

Solution 3

The third solution is to not allow client side scripts access to your cookies. By default, client side JavaScript is able to read your cookies. This means that any third party JavaScript you are including has the potential to read your cookies and use the session id for nefarious purposes.

In order to stop this, you need to use the httpOnly flag for your cookies. This flag, when set in the response that sets the cookie, will tell the browser to not allow any client side JavaScript access to it. The cookie is only sent over requests.

The following Express application from previous examples is updated to show how to do this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var express = require('express');
var session = require('express-session');

var app = express();

app.use(session({
  secret: 'our super secret session secret',
  cookie: {
    maxAge: 3600000,
    secure: true,
    httpOnly: true
  }
}));

app.listen(80);

Here is an example of what the header looks like with both the secure and httpOnly flags set.

1
Set-Cookie: connect.sid=fjdskfjdsksdfjksa; Domain=favatron.com; Path=/; Expires=Sun, 28 Jun 2015 02:12:41 GMT; HttpOnly; Secure

Solution 4

The fourth solution is to tell the browser to never make HTTP requests again. There is a great response header you can send back on all responses that tells the browser to only make requests over HTTPS for a certain amount of time. This response is called HTTP Strict Transport Security (HSTS).

This is a great feature, because it negates the problem where users may still make requests over HTTP to your site. Then at the most, users would make one request over HTTP. From that point forward, they would only make requests over HTTPS.

The following Express application from previous examples is updated to show how to do this using the helmet package.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var express = require('express');
var session = require('express-session');
var helmet  = require('helmet');

var app = express();

app.use(helmet.hsts({
  maxAge: 7776000000,
  includeSubdomains: true
}));

app.set('trust proxy', 1);

app.use(session({
  secret: 'our super secret session secret',
  cookie: {
    maxAge: 3600000,
    secure: true,
    httpOnly: true
  }
}));

app.listen(80);

Here is what the response header would look like.

1
Strict-Transport-Security: max-age=7776000; includeSubDomains

Along with using the response headers to control this, there is a registry where you can add your domain and have it automatically added to preload lists of Chrome, Firefox, Safari, and a future version of IE. This way, even that initial request over HTTP could be avoided. You can find the form here.

Wrap up

There are many more ways in which you can create broken authentication and session management. Hopefully this article has given you some insight into areas of your application to pay attention to.

I have a lot more tutorials coming so be sure to subscribe to my RSS feed or follow me on Twitter. Also, if there are certain topics you would like me to write on, feel free to leave comments and let me know.