Evercookie: the cookie that just won't die

Samy, famous for his worm, released evercookie this week. Evercookie stores cookies is various storage mechanisms such as Flash Local Shared Objects (also known as flookies), HTML5 storage mechanisms and even in the history and cache. When any of these are wiped by the user the script will repopulate it, making it very hard to get rid of your cookies.

This is technique is common to circumvent a users' privacy wishes, which Clearspring recently got sued for, but it's put in overdrive.

One good use for it is banning users. In the past I've used ips + cookies to ensure a user stays banned, but it doesn't take much to change your ip address and clear your cookies. All these techniques together make it a lot harder to get through. Because Flash stores it's flookies in a central place in the operating system, the cookies often even live in multiple browsers and private browsing sessions.

Most of all, I think the tool is made to make a point. It's very hard for the average user to clear all the tracking information. It should be doable with a press of a button, without losing all your settings and history for every other site.

Storing encrypted session information in a cookie

cookie

Our session system is due for an upgrade. Currently all PHP sessions are stored in the database, and some things are getting a bit slow. There have been a couple of approaches I've been considering, one of which is simply storing all the information in a browser cookie.

First I want to make clear I don't necessarily condone this. The reason I'm writing this post, is because I'm hoping for some more community feedback. Is this a really bad idea? I would love to know.

The benefits

If all the session data is stored in the browser, it means that I don't need to store it on the server. I actually don't care all that much for having the data on the server (unless it's the only secure way), it's mostly a gigantic map with session tokens and user id's (along with some other info).

I also feel it's more natural for HTTP, as it makes it a bit more stateless.

Sample code

  1. <?php
  2.  
  3. class BrowserSession {
  4.  
  5. public $secret = 'this will need to be a cryptographic random number';
  6. public $currentUser = null;
  7.  
  8. // Sessions time out after 10 minutes
  9. public $timeout = 600;
  10.  
  11. function init() {
  12.  
  13. if (!isset($_COOKIE['MYSESSION'])) {
  14. echo "No session cookie found\n";
  15. return;
  16. }
  17.  
  18. list($userId, $time, $signature) = explode(':',$_COOKIE['MYSESSION']);
  19.  
  20. // The cookie is old
  21. if ($time> time() + $this->timeout) {
  22. echo "The session cookie timed out\n";
  23. }
  24.  
  25. if ($signature !== $this->generateSignature($userId,$time)) {
  26. echo "The secret was incorrect\n";
  27. }
  28.  
  29. $this->currentUser = $userId;
  30.  
  31. echo "Logged in as user: $userId\n";
  32.  
  33. }
  34.  
  35. function login($userId) {
  36.  
  37. $this->userId = $userId;
  38.  
  39. $time = time();
  40.  
  41. $cookie = $this->userId . ':' . time() . ':' . $this->generateSignature($userId,$time);
  42.  
  43. setcookie('MYSESSION',$cookie,$time+$this->timeout,null,null,null,true);
  44.  
  45. echo "Set cookie: $cookie\n";
  46.  
  47. }
  48.  
  49. function generateSignature($userId,$time) {
  50.  
  51. $stringToSign =
  52. $userId . "\n" .
  53. $time . "\n" .
  54. $_SERVER['HTTP_USER_AGENT'] . "\n" .
  55. $_SERVER['REMOTE_ADDR'];
  56.  
  57. return hash_hmac('SHA1',$stringToSign,$this->secret);
  58.  
  59. }
  60.  
  61. }
  62.  
  63. ob_start();
  64. $session = new BrowserSession();
  65. $session->init();
  66.  
  67. if (isset($_GET['login'])) $session->login($_GET['login']);
  68. else {
  69.  
  70. echo '<br /><a href="?login=1234">Log in as user 1234</a>';
  71.  
  72. }
  73. ?>

A few notes:

  • The preceeding code was just intended as a proof of concept, it's missing some validation.
  • Currently the secret would be the same for every user. I was thinking of appending some per-user information to the secret. If somebody does guess or bruteforce the secret, they would only have access to a single users' information.
  • If a user changes their password, existing sessions should expire. To do this the signature should also include a sequence number that changes when the password changes.
  • Currently this only stores a user id. It could be extended to contain more data, but this is all I need.

So, is there anything fundamentally wrong with this approach? In general the client should never be trusted, but for setups where the security requirements aren't as high (highly subjective, I know) I feel this might be strong enough. OAuth, OpenID and Amazon AWS all seem to trust HMAC+SHA1, but those applications do work differently.

Credit where it's due

I first asked this question on stack overflow. The users there already gave some great suggestions and pointed out some of the flaws. Thank you!

Firefox gets httpOnly cookies

httpOnly cookies allow you to hide your (session-)cookies from javascript. In the case of an XSS hole in your application, it will make a hackers life much harder to steal someones session.

Internet Explorer support httpOnly cookies for a long time, but since version 2.0.0.5 Firefox also supports this feature. Apparently Mozilla hasn't openly promoted this new feature yet, because its still possible to fetch the cookies with XMLHttpRequest. PHP has support for httpOnly cookies and sessions since 5.2.

Sharing sessions between html and flash

cookieThis has been an issue that has been driving me pretty crazy.. I can't seem to find out how to share a (cookie-)session between flash and php.

The problem is that in certain situations Flash ignores session cookies when sending requests. The situations I know of are Flash Uploads and using Flash Remoting in internet explorer.

I asked my question on #webappsec and on the web application security mailing list, but there wasn't really somebody who could answer my quesion..

Options

  1. I can pass the session id using flashvars directly. Problem with this is, is that the session id is directly embedded into the html and can therefore be stolen using CSRF.
  2. I can use a temporary token, but anybody who has this token can do everything the user can in the flash application. For just the uploads it can work, but for everything else its not really flexible, and doesn't really fix the problem.
  3. I could turn off httponly cookies and pass the session id using javascript straight to the flash movie.. This could be me only option, but I dislike it because its not as transparent as it should be and requires additional logic using javascript and flash (and php).
  4. Force the user to login when using flash.. Not really a nice solution from a usuability perspective..

I'm wondering how other people go about this.. Is there a satisfying solution at all? Or can it only be done using a combination of nasty hacks?

On HttpOnly, Firefox-specific XSS and this years major Livejournal XSS attack

Yep, thats a long title, but they are all related to each other in some way. In the first few paragraphs I will explain what cookies are and XSS. You might want to skip ahead if you already know what this is.

Sessions, Cookies

HTTP is stateless. This means that every request to the server is a 'new' one and normally there is no relation to a first or second request. To allow maintaining a session or 'state' between multiple requests, HTTP cookies are used.

A cookie is basically a HTTP header with a tiny piece of information that gets re-sent with every request to the server. A popular way to make use of this is through PHP's$_SESSION system. This sends a cookie with a unique id to the client that allows PHP to retain a users' information across pages.

XSS

If you allow users to for example comment on one of your pages and allow (certain) html, it is sometimes possible to inject a piece of javascript. There are many tricks to evade the so-called html sanitizers.. strip_tags() is PHP's built-in sanitizer, but it doesn't work really well.. if, for example, you would allow users to use a <p> tag, which might seem harmless, there would be tricks to abuse the style="" or onclick="" attributes, just to name a few.

XSS and cookies

So how do you abuse javascript and cookies combined?

Because with for example PHP's session system, you can use the contents of the cookie to steal someone's session. The hacker would be logged in as you and might able to change your password and log you out afterwards. The contents of the cookies is stored in the javascript variable document.cookie.

HttpOnly, a solution

Microsoft came up with a way to prevent this from happening, ever since Internet Explorer 6.0 (starting from Windows XP SP1). They added an extra piece of information to a cookie, that will still allow the use of cookies in the way you are used to, but it will prevent the cookie from being read by javascript (basically it is invisible for javascript). Be sure to check out microsofts spec at MSDN

Safari and Opera quickly started supporting this. Because of this it is becoming pretty useful to use in practice. Remember that this doesn't mean you can just accept any html on your site, you should still always sanitize the bad stuff or not allow it at all! But in the case you missed something, it can make it a lot more difficult for your attacker to steal sessions.

UPDATE: Safari/Opera actually ignore it, my excuses, I didn't check my sources.

Under the hood

Normally, a cookie header will look like this:

  1. Set-Cookie: USER=username; expires=Wednesday, 09-Nov-99 23:12:40 GMT;

But with the HttpOnly, it will look like this:

  1. Set-Cookie: USER=username; expires=Wednesday, 09-Nov-99 23:12:40 GMT; HttpOnly
.

It's a small change, and all normal browsers should still accept this even if they don't understand the HttpOnly part. There is an exception though, and it goes by the name of IE 5 for mac. This browser won't understand the cookie and totally ignores it. Personally I don't support this browser for any application anymore, as there are too many bugs in this browser. But if your boss wants it, this might prevent you from using HttpOnly

PHP support

A guy named Scott MacVicar created a patch for PHP that will add an extra parameter to set_cookie() to enable HttpOnly for your cookies. The patch should also enable this by default for the session system.

If the patch will get accepted we will likely be able to use this in PHP 6.0 and perhaps even PHP 5.2, I'm looking forward to that. There is a chance though, because of the IE5/mac breakage that it eventually won't be auto-enabled for the session system.

So what about firefox?

Firefox doesn't support it, there is currently a bug open for it (Bug #178993). There was an initial solution posted over 2 years ago (January 2004). And a few other patches later on, but the mozilla folks refused all of them because they want to maintain the exact format of cookies.txt (the file they use to store cookies), because other applications might rely on that format. A few solutions for that have been posted, but it doesn't seem like a high priority for them.

A workaround for firefox (kind of)

There have been solutions for firefox that also blocked reading of cookies by javascript. Firefox has a magic function __defineGetter__ that can block reading of variables. To do this for all cookies on your site, include the following snippet on top of your html page:

  1. HTMLDocument.prototype.__defineGetter__("cookie",function (){return null;});

However, you can't rely on this! There are still ways to get this cookie if the hacker can somehow create an iframe in your html page. The hacker has a reference to the same cookies if he uses the data: protocol in the src="" attribute. This will still make it a bit harder to steal cookies, so it's not a bad idea to implement. For a longer explanation of this workaround, check out http://www.wisec.it/sectou.php?lang=en.

The LiveJournal case

The same people who submitted the initial firefox-patch (see above) 2 years ago, also got hit by the attack in January 2006. Over 46% accounts were hijacked. This were over 900.000 stolen accounts. (check out their post about their solution.)

The reason they got hit by this is because they allowed users to use remote CSS stylesheets for their pages. CSS used to be only a specification for how html elements would look like on a page, but since a few years it has become more dynamic and now there are ways to exploit CSS with XSS attacks through for example IE's non-standard behavior: attribute and Firefox' -moz-binding: attribute (there are more you can exploit, but its outside the scope of the article). These attributes allow an author to create a custom behavior for a HTML element. The technique to do this for Firefox is called XBL.

Normally, when a page is loaded from domain A, and another (in a different frame for example) is loaded from domain B. Malicious scripts from domain B can never access cookies from domain A. This is called a 'same origin check'. This generally works in all browsers for all kinds of content, but an exception is XBL. (see bug #324253.)

Apparently this requires some major changes in how firefox works. The last comment in this bug is from February this year (2006). I hope they will wake up some day soon and make our life a little bit easier by both supporting HttpOnly and securing XBL.

So the LiveJournal attack could happen because the hackers used XBL in their CSS, which in turn accessed the HTTP cookies through javascript. The attack didn't affect users of other browsers, because: A) they support HttpOnly, B) even if they wouldn't.. the microsoft equivalent of XBL, which is .HTC files, don't work across different domains.

 1

About

My name is Evert, and I've been writing semi-regularly on this blog since 2006.

I'm currently available for contract work.

more info.

Subscribe

Dropbox

Dropbox is a simple cross-platform online backup and sync application. The first 2GB of space is free, and both you and me get an extra 250MB extra space if you sign up through this link.