Spare Pencil

April 15 3 comments
Watch the replies

Airtight sessions

I recently read an article which warns about the fact that cookie based authentication can be exploited using JavaScript. I am not going into detail about the problem itself, but I’ll try to give a brief explanation using an example:
Alice is a user browsing the web. She is logged in on site A using a session ID which is conveniently stored in a cookie on her PC. Meanwhile, she browses around a bit and stumbles upon site B. Site B contains an evil piece of JavaScript code that makes a request at site A. There is a loophole in Alice’s browser’s (which could be any modern browser) same origin policy: The request is submitted with the session cookie for site A. The request is automatically accepted by site A, because the session was authorised, but Alice has no idea (until she finds out that something disastrous has happened to her account).

You might think the browser is the weak link here, because it doesn’t handle the cookie correctly. That is true, but apparently it is difficult to flawlessly apply the same origin policy. I might be wrong though, but it doesn’t really matter for now, because this article focuses on something else.

The server side application at site A could have been protected against the attack. In this article, I will highlight a few methods that could make the process safer. Airtight sessions.

As developers of website applications, we will have to avoid relying on cookie data contained in the HTTP Cookie header when performing certain actions. These certain actions are basically the ones that require authentication (e.g.. changing user preferences, making a blog post, digging a story, etc.)
There are two ways in which this can occur: 1) using an HTML form (with POST data) and 2) using the XMLHttpRequest object. The difference is that the latter uses JavaScript and the first does not (or does not have to, at least).
We’ll have a look at the HTML form scenario first.

The HTML form way

The following form allows an authenticated user to change his or her email address (just try to imagine it, if you don’t agree).
It is generated by a PHP script (doesn’t matter for now, but will be useful later),

  1. $form = '<form action="change_email.php" method="post" enctype="application/x-www-form-urlencoded"> <input name="email" type="text" /> <input name="change_email" value="Change" type="submit" /> </form>';
  2. echo $form;

The relevant part of the PHP script that takes care of this form (change_email.php) looks like this:

  1. session_start();
  2.  
  3. if(true != $_SESSION['logged_in'])
  4. {
  5.   exit;
  6. }
  7.  
  8. if(isset($_POST))
  9. {
  10.   // Deal with the form…
  11.   // …

Note that the user is authenticated by means of the session variable logged_in. The session ID is, of course, obtained via a cookie.

We don’t want this, because the cookie might not be safe. Instead, we will use a custom solution. (There may be other possibilities to make this form safer, but I will demonstrate just one.)
To avoid using a cookie, we will make the session ID part of the form. The form code snippet will look like this:

  1. $form = '<form action="change_email.php" method="post" enctype="application/x-www-form-urlencoded"> <input name="email" type="text" /> <input name="change_email" value="Change" type="submit" /> <input name="session_id" value="' . session_id() . '" type="hidden" /> </form>';
  2. echo $form;

When the form is submitted, it will also pass the session ID. Easy, right?

Now on to the change_email.php script. We must tell the session handler that we want to use the POSTed session ID instead of the one in the cookie (which is the default).
The relevant part is slightly rewritten:

  1. if(isset($_POST))
  2. {
  3.   $safe_id = (isset($_POST['session_id']) ? $_POST['session_id'] : null;
  4.   session_id($safe_id);
  5. }
  6. session_start();
  7.  
  8. if(true != $_SESSION['logged_in'])
  9. {
  10.   exit;
  11. }
  12.  
  13. if(isset($_POST))
  14. {
  15.   // Deal with the form…
  16.   // …

Lines 2 through 6 make sure the POST request is handled using the session that was set in the form. Line 4 may seem kind of useless, but it prevents an E_NOTICE from being triggered (it’s become a habit of mine to code like this…)
The session_id() function allows us to change the session ID manually before starting the session.

This method should stop malicious JavaScript on external sites from abusing your cookies.

The XMLHttpRequest way

Imagine the previous situation of changing an email address, but then in AJAX style. I am not going into detail regarding the usage of the XMLHttpRequest object, because there are a bunch of frameworks that all work a bit differently.
Just keep the following points in mind:

So, basically, you will need to add the session ID cookie to the POST data. Luckily, you can use JavaScript to extract the ID from the existing cookies:

  1. function read_cookie(name) {
  2.   var nameEQ = name + "=";
  3.   var ca = document.cookie.split(';');
  4.   for(var i=0;i &lt; ca.length;i++) {
  5.     var c = ca[i];
  6.     while (c.charAt(0)==' ')
  7.       c = c.substring(1,c.length);
  8.     if (c.indexOf(nameEQ) == 0)
  9.       return c.substring(nameEQ.length,c.length);
  10.   }
  11.   return null;
  12. }
  13.  
  14. session_id = read_cookie('PHPSESSID');
  15.  
  16. // Include session_id in your POST data…

On line 14, PHPSESSID is the name of the session. It is the default, but if you use a different one, you should keep that in mind. (You can use the session_name() function to find it out.)

This code works because document.cookie is only ‘allowed to look at’ cookies that are associated with the current document (so external sites don’t stand a chance).

Conclusion

Cookies are a nice means of keeping sessions going. But when users of your web application perform sensitive tasks, you should not simply rely on a cookie for authentication.
The increasing possibilities of JavaScript can greatly enhance the usability of your application, but it also makes it harder for the browser (but also the developer) to keep things secure. Therefore, make sure sessions are airtight whenever there is interaction between the client and the server.

This is my first real article here, so if I forgot to mention basic things or something else, please tell me.


3 Comments

Write one, too!

Reply Here




You may use some markup in your comment, unless you're drunk.
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>