Help Prevent Session Hijacking

Keep your cookies to yourself

Browser cookies are a good way to provide a stateless protocol with some memory. Unfortunately, they're also good for hijacking application sessions and impersonating users. There are a number of attacks that utilize improper use of session cookies. Session hijacking is among the most common and potentially destructive cookie attacks.

What Is Session Hijacking?

First, some context. Cookies are used for a variety of reasons. They may store user preferences, track a user’s browsing habits, or authenticate a user without requiring him/her to enter usernames and passwords on every request. There are other uses for cookies but session hijacking focuses on authentication cookies, known as session tokens.

Because session tokens are so common, Rails provides one automatically for every application. Typically, when a user logs in, an identifier is saved inside this session token for future authentication. This usually looks something like:

1 session[:user_id] = user.id

The convenience this session token provides does not come without a cost. If this cookie is stolen, an attacker can pose as the compromised user with all of their privileges. The theft can be accomplished in several ways: predictable session token value, man-in-the-middle attack, cross-site scripting, and others. This attack is known as session hijacking.

Unpredictability

The first step that should be taken to protect your cookies is to use an unpredictable value inside the cookie. For example, a simple value for a session cookie might be the user’s id. The following is the worst way you could store this id in Rails.

1 cookies[:secure_session] = user.id

Cookies can be easily modified. Therefore, after someone logs in to this application, they can simply change the value of the cookie stored in their browser to hijack another user’s session. If the value of the cookie is always 35, for example, it is easy to guess that there is a user with an id of 34. Change the cookie value to 34 and you are now that user. A minor change to the cookie makes the same value unpredictable.

1 cookies.signed[:secure_session] = user.id

Calling the signed method on cookies encrypts the value. It does this by using the ActionSupport key generator along with your application’s secret token that is automatically generated by Rails. Doing so virtually prevents the value from being tampered with. (You can find the unique secret token in config/initializers/secret_token.rb.)

The session token that Rails provides uses an unpredictable value by default, known as the session ID. Therefore, it is already protected against the easiest method for hijacking a session - prediction.

SSL Is Important

It doesn’t matter how unpredictable the session token’s value is, if it is being sent from the client to the server in the clear, it is not secure. Firesheep, the popular Firefox add-on, made this fact painfully obvious by providing an easy way to hijack sessions that are not encrypted. This means that in order to keep cookies safe, they must be transmitted via SSL, every time.

Without the encrypted tunnel that SSL provides, cookies can be intercepted in transit making the interceptor indistinguishable from the real user. This form of session hijacking is easily accomplished at the local coffee shop or any other establishment that offers open, unencrypted WiFi connectivity. This is because the key used to identify a user is being passed openly through the air for anybody to grab.

There are a few ways to ensure cookies are always transmitted via SSL. The most complete method is by forcing a secure connection for every request. This is done in Rails 3.x by adding the following line to your application config file (config/application.rb).

1 config.force_ssl = true

Another, less draconian, method is to set the cookie as “secure”. This ensures the cookie itself will only be transmitted over an SSL connection. This is done in rails by setting the secure hash value of the cookie to true:

1 cookies.signed[:secure_session] = {secure: true, value: "#{user.salt}#{user.id}"}

Keep in mind that if you try to send the cookie over a non-encrypted connection, the request will be allowed but the cookie will not be sent.

Rails’ session token cannot be set to be transmitted via secure connection only. It is intended to be sent over every connection made by your app. Therefore, either force ssl for every request, or use a secure cookie for authentication. Transmitting a session token used for authentication over an insecure connection increases the risk that your user's session may be hijacked.

Sessions Should End

The life of a cookie is configurable. The Rails session token is marked as “session” so it is deleted automatically when the browser closes. By default, any cookie created in Rails is also set to expire with the session. You can, however, cause cookies to stick around much longer by setting the “expires” attribute.

1 cookies.signed[:secure_session] = {expires: 1.year.from_now, value: "#{user.salt}#{user.id}"}

This cookie will stay on the user’s computer for a year unless it is removed through the browser’s cookie removal tools. Persistent cookies allow a user to remain logged in for the given period of time without the need to enter their credentials again. However, they also give a hijacker a larger window for exploitation.

Because session hijacking is a real threat, these cookies should remain short lived. Many web apps offer “remember me” check boxes that allow users to remain logged in after the normal session ends. This is one of those trade offs between convenience and security.

Regardless of the life of your session cookie, a sign out button should be easy to find on every page of your application. Make it easy for users to destroy their session when they want to.

HTTP Only

There are several cross site scripting (XSS) attacks aimed at stealing session cookies. Setting the cookie’s HttpOnly attribute prevents the cookie from being transmitted via scripts, greatly reducing the risk of XSS. The Rails provided session token sets this value by default. To accomplish this with your custom cookies:

1 cookies.signed[:secure_session] = {httponly: true, value: "#{user.salt}#{user.id}"}

Restrict The Domain

As is the case with most security choices - the more restrictive, the better. This is why whitelisting is typically preferred over blacklisting. For cookies, this means restricting the domain and path.

The “domain” attribute sets the cookie to only be sent to the specified domain or subdomain. If the attribute is not explicitly set, the default value is set to only allow the cookie to be sent to the origin server. If the application requires a broader set of permissions, it can be accomplished by specifying the domain attribute upon cookie creation.

1 cookies.signed[:secure_session] = {domain: 'example.com', value: "#{user.salt}#{user.id}"}

Cookies with broad domain permissions are known as cross-subdomain cookies. The problem with broad domains is that it allows an attacker to compromise a single cookie and use it across different applications that belong to the same domain. A vulnerability in one application may automatically open a vulnerability in another application. A better approach might be:

1 cookies.signed[:secure_session] = {domain: 'accounts.example.com', value: "#{user.salt}#{user.id}"}

The “path” attribute is similar to the “domain” attribute. It tells the browser to only send the cookie to the specified directory or subdirectory. Once again, a restrictive path is typically more secure.

1 cookies.signed[:secure_session] = {path: '/users/pages', value: "#{user.salt}#{user.id}"}

Conclusion

There are a number of things that you can do to your cookies to make them more secure. Make sure the value within the cookie is completely unpredictable, transmit session cookies via SSL, make it easy to end sessions, set the HTTP attribute on the session cookie to true, and restrict the domain and path as much as possible. There is always more that can be done but these will get you started.

Cookies can be the source of many security vulnerabilities in a web application. Session hijacking is one of the most common problems caused by improper handling of these convenient functions of the Internet. Pay close attention to what you do with cookies and help make the Internet a safer place.

Adam Gooch was an 8th Light Software Craftsman.