Random notes http://mixedbit.org/blog/ en-us Mon, 18 Aug 2014 00:00:00 +0000 http://mixedbit.org/blog/2014/08/18/shapespark.html http://mixedbit.org/blog/2014/08/18/shapespark.html <![CDATA[Shapespark]]> Shapespark

Shapespark is a company that my friend Wojtek and I have started, and it has been the center of our attention during the last couple of months.

Our goal is to build a tool for professional architects and interior designers to create WebGL-based real-time interactive visualizations. We focus on the quality of visualizations, utilizing physically-based rendering techniques to calculate lighting, which is crucial for giving 3D scenes a sense of realism. We are also experimenting with Oculus Rift, which turns out to be a quite useful tool for presenting architectural designs.

Our first visualization has already received compliments from many WebGL enthusiasts, including Mr.doob, the famous author of the Three.js library :D.

Overall, this is a very interesting venture, with so many new areas to explore. You can expect more WebGL-related content and demos on this blog, although we will probably start a Shapespark blog at some point.

]]>
Mon, 18 Aug 2014 00:00:00 +0000
http://mixedbit.org/blog/2014/01/08/wwwhisper_add_on_for_node_js.html http://mixedbit.org/blog/2014/01/08/wwwhisper_add_on_for_node_js.html <![CDATA[wwwhisper add-on for Node.js]]> wwwhisper add-on for Node.js

The wwwhisper Heroku add-on is now available for Node.js. As with the Ruby Rails/Rack version the setup is just a matter of three lines of config that enable the wwwhisper auth middleware.

One of the goals of wwwhisper is to decouple authentication and authorization logic from the application business logic. Such approach allows to easily enable auth without changing business logic at all, but it has also security benefits. The Open Web Application Security Project summarizes it nicely:

‘A common mistake is to perform an authorization check by cutting and pasting an authorization code snippet into every page containing sensitive information. Worse yet would be re-writing this code for every page. Well written applications centralize access control routines, so if any bugs are found, they can be fixed once and the results apply throughout the application immediately.’
]]>
Wed, 08 Jan 2014 00:00:00 +0000
http://mixedbit.org/blog/2013/05/13/object_oriented_programming_with_closures.html http://mixedbit.org/blog/2013/05/13/object_oriented_programming_with_closures.html <![CDATA[Object Oriented Programming with closures]]> Object Oriented Programming with closures

I’ve been recently watching Structure and Interpretation of Computer Programs. These are great lectures that I highly recommend to everyone interested in programming. Some people can be discouraged by the use of Scheme, don’t be. Scheme is a tiny language, very easy to pickup and follow, and many techniques from the lectures are applicable to today’s mainstream languages.

One interesting idea is that closures are enough to implement an object-oriented system. A simple example is a counter object. In JavaScript it can be implemented like this:

function counter() {
  var val = 0;
  function get() {
    return val;
  }
  function inc() {
    val += 1;
  }
  return [get, inc];
}

function get_count(counter) {
  return counter[0]();
}

function inc_count(counter) {
  counter[1]();
}

var counter = counter();
inc_count(counter);
inc_count(counter);
console.log(get_count(counter)); // prints 2

A statefull counter exposes a public interface for increasing and obtaining a current count. The internals are encapsulated, you can not reset or decrease the count, because such operations are not exposed by the API.

You can easily define your own version of the counter, and use it in any place where the counter is expected:

function fast_counter() {
  var val = 0;
  function get() {
    return val;
  }
  function inc() {
    val += 2;
  }
  return [get, inc];
}

The implementation above makes one shortcut: a JavaScript built-in Array object is used to store and dispatch counter’s internal methods. This is convenient, but not necessary. The lectures show that closures are enough to implement data structures without a need for any built-in complex data types (‘out of thin air’ as lecturers call it). The trick is stunning and really worth studying. First we need a function that creates a pair:

function pair(first, second) { // In Lisp called `cons`
  function set_first(value) {
    first = value;
  }
  function set_second(value) {
    second = value;
  }
  return function(handler) {
    return handler(first, second, set_first, set_second);
  };
}

A pair returned by the function is not data, but code: a closure. The closure needs to be invoked with a function as an argument, and it gives this function permissions to access and modify the values stored in the pair.

Here is how the first element of the pair can be extracted:

function first(pair) { // aka car
  return pair(function(first, second, set_first, set_second) {
    return first;
  });
}

first() invokes a pair() and passes a function to it. The function is then invoked by the pair with its two current values and two functions to change the current values. first() cares only about the value of the first element and ignores remaining arguments.

Below are three more analogous functions to operate on the pair:

function second(pair) { // aka cdr
  return pair(function(first, second, set_first, set_second) {
    return second;
  });
}

function set_first(pair, value) { // aka set-car!
  return pair(function(first, second, set_first, set_second) {
    set_first(value);
  });
}

function set_second(pair, value) { // aka set-cdr!
  return pair(function(first, second, set_first, set_second) {
    set_second(value);
  });
}

The internals are hairy, but resulting high level interface is simple:

var p = pair(3, 4);
console.log(first(p) + " : " + second(p));
set_first(p, 5);
console.log(first(p) + " : " + second(p));

And pairs are enough to build a list:

var l = pair(3, pair(4, pair(5, null)));

Of course, a higher level interface can be defined, this time very straight-forward:

function list() {
  return null;
}

function is_empty(list) {
  return list === null;
}

function last(list) {
  if (is_empty(list) || second(list) === null) {
    return list;
  }
  return last(second(list));
}

function length(list) {
  if (is_empty(list)) {
    return 0;
  }
  return 1 + length(second(list));
}

function append(list, value) {
  if (is_empty(list)) {
    return pair(value, null);
  }
  set_second(last(list), pair(value, null));
  return list;
}

function get_item(list, idx) {
  if (idx === 0) {
    return first(list);
  }
  return get_item(second(list), idx - 1);
}

var l = list();
console.log(is_empty(l));
l = append(l, 1);
l = append(l, 2);
l = append(l, 3);
console.log(is_empty(l));
console.log(get_item(l, 1));
console.log(length(l));

This is just an exercise, even in Lisp pairs (cons cells) are unlikely to be implemented this way. But it is interesting to see how powerful closures are, and how they allow to blur the distinction between code and data.

]]>
Mon, 13 May 2013 00:00:00 +0000
http://mixedbit.org/blog/2013/04/24/wwwhisper_add_on_released.html http://mixedbit.org/blog/2013/04/24/wwwhisper_add_on_released.html <![CDATA[wwwhisper add-on released]]> wwwhisper add-on released

The authorization add-on for Heroku is finally out of beta. A huge thank you to everyone who has tried the add-on during the testing phases!

Plans and pricing

Three plans are currently available:

  • Starter is a free plan that allows to grant access to a protected application to a single user. The plan gives an easy way to try wwwhisper. It should also cover the needs of people who are developing web applications alone or who would like to conveniently host any privately accessible application on Heroku.
  • Basic plan allows to grant access to an application to up to ten people. The plan should cover the needs of a team working together, or sharing an application with clients or early adopters. It is also intended for small publicly accessible sites that need to have some locations protected, for example an admin web interface. The add-on allows to easily restrict access to such locations and frees developers from worrying about the authorization logic. It can also provide protection if an available authorization mechanism has security holes.
  • Plus plan supports sites with bigger traffic and allows for up to 100 authorized users.

Roadmap

If you have any feedback about available features or pricing please send an email to wwwhisper-service@mixedbit.org. This will help greatly to prioritize a todo list. Currently following items are considered:

  • Authorization middleware for more frameworks (node.js, wsgi, …).
  • An option to set custom texts on a sign-in page.
  • Improvements to the admin web interface to conveniently manage 100+ users.
  • A plugin for a heroku command line tool to perform wwwhisper admin operations (grant, revoke access).
  • Allow to use the service outside of the Heroku platform.
]]>
Wed, 24 Apr 2013 00:00:00 +0000
http://mixedbit.org/blog/2013/04/11/dos_attack_on_cdn_users.html http://mixedbit.org/blog/2013/04/11/dos_attack_on_cdn_users.html <![CDATA[DoS attack on CDN users]]> DoS attack on CDN users

Sibling domains cookie isolation got some publicity recently when GitHub moved user generated pages to github.io. The problem is not new, but many sites still ignore it. One issue that somehow escaped popular perception is that cookie isolation policy can be exploited to break sites that depend on content hosted on Content Delivery Networks.

The issue affects most CDN providers, let me use RackCDN for illustration. A malicious JavaScript embedded in an HTML file served from foo.rackcdn.com subdomain (visited directly or iframed, so its origin is foo.rackcdn.com) can set a large number of large cookies with a domain attribute set to rackcdn.com. Such cookies are then sent with all future requests to all *.rackcdn.com subdomains.

Firefox allows combined size of cookies for a domain to have 614KB, Chrome 737KB, IE 10KB. After a visit to a malicious site that iframes the cookie setting HTML from foo.rackcdn.com, each subsequent request to any subdomain of the CDN is going to carry many KB of cookies. This effectively prevents the browser from accessing any content on *.rackcdn.com. HTTP servers are configured to limit the size of HTTP headers that clients can sent. Common and reasonable limits are 4KB or 8KB. If the limit is exceeded, the offending request is rejected. In my tests I haven’t found a single server that would accept ~0.5MB worth of headers, popular responses are:

  • TCP Reset while the client is still sending.
  • HTTP error code 4XX (400, 413, 431 or 494) and TCP reset while the client is still sending.
  • HTTP error code 4XX after a request is fully transferred.

Even if CDN servers accepted such large requests, the overhead of sending the data would terribly affect performance.

A nasty thing is that a malicious site does not need to target a single CDN, the site can use iframes to include cookie setting HTML from multiple CDNs and prevent a visitor from accessing considerable part of the Internet. Note that this is not a DoS on CDN infrastructure, for which 0.5MB requests should be insignificant.

CDN level protection

To protect Firefox, Chrome and Opera users CDN providers should make sure a domain that is shared by their clients is on the Public Suffix List. If a CDN provider uses bucket.common-domain.xyz naming scheme (like for example foo.rackcdn.com), common-domain.xyz should be a public suffix. For more elaborate schemes, such as bucket.something.common-domain.xyz (for example: a248.e.akamai.net), *.common-domain.xyz should be a public suffix.

IE does not use the list, but IE limits combined size of cookies to only 10KB, CDN servers could be configured to accept such requests. The attack would still add a significant overhead to each request, but at least the requests were served. Hopefully, with issues like this exposed, IE will switch to the Public Suffix List at some point.

Web site level protection

The only things a web site that depends on a CDN can do to ensure the CDN hosted content can be accessed by users is to either choose a CDN that supports custom domains, choose a CDN that is on the Public Suffix List, or ask a CDN provider to add a shared domain to the list. A custom domain can be problematic if the site wants to retrieve content from CDN over HTTPS (a wise thing to do). While CDN services are cheap today, CDN services with custom domains and HTTPS are quite expensive.

Browser level protection

To efficiently prevent the attack, users would need to configure browsers to by default reject all cookies and allow only white-listed sites to set cookies. There are extensions that allow to do it quite conveniently. Similarly rejecting by default all JavaScript and white-listing only trusted sites fully prevents the attack.

Disabling third party cookies mitigates the problem. While a malicious site can still set a cookie bomb, it now needs to be visited directly (not iframed), and it can target only a single CDN at once. The new Firefox cookie policy, to be introduced in the release 22, should have the same effect.

Affected CDNs

All CDN providers that use a shared domain that is not on the Public Suffix List are affected. This is a long list, I’ve directly contacted four of them:

Amazon confirmed the problem and added CloudFront and S3 domains to the Public Suffix List.

RackSpace and Akamai did not respond.

Google did not confirm the issue with CloudStorage. Google deploys a JavaScript based protection mechanism that can work in some limited cases, but it my tests it never efficiently protected CloudStorage.

When Google server receives a requests with headers that are too large, it responds with HTTP error code 413 and an HTML with a script that should drop the offending cookies (white spaces added by me):

if(!location.hostname.match(/(^|\.)(google|blogger|orkut|youtube)\.com$/)) {
  var purePath=location.pathname.replace(/\/[^\/]*$/,"");
  var cookieSet=document.cookie.split(";");
  for(var cookieNum=0;cookieNum<cookieSet.length;++cookieNum){
    var name=cookieSet[cookieNum].split("=")[0];
    var host=location.hostname;
    while(1){
      var path=purePath;
      while(1){
        document.cookie = name+"=;path="+path+"/;domain="+host+
          ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
        var lastSlash=path.lastIndexOf("/");
        if(lastSlash==-1)
          break;
        path=path.slice(0, lastSlash)
      }
      var firstDot=host.indexOf(".",1);
      if(firstDot==-1)
        break;
      host=host.slice(firstDot)
    }
  }
  setTimeout("location.reload()",1E3)
};

This code loops through all cookies, and overwrites them with expired values. Because a path and a domain are parts of a cookie id, but are not exposed by browsers API, the cleaning code needs to cover all possible paths and domains (hence three loops). This works fine if the code is executed. The problem is that the HTTP server resets the connection just after returning the response, while the client is still sending the request. Browser does not interpret the response as HTTP level 413 error, but TCP level connection reset error. The servers would need to let clients send the complete request for the response to be interpreted. Another problem is that the code is executed in a context of *.commondatastorage.googleapis.com domain only when the browser requests an HTML from this domain directly or via iframe. If a browser is requesting images, scripts or css, which is most often the case with CDNs, cookie clearing JavaScript has no chances of being executed.

Other affected sites

CDNs are the most interesting targets for a DoS attack like this, but many sites can be also targeted directly. I’ve notified GitHub and DropBox, both were already working on moving user generated pages to separate domains (now the move is completed). While this protects github.com and dropbox.com, a user content can still be a subject of DoS. Similarly it is easy to cut access to Google drive hosted static pages or Tumblr.

]]>
Thu, 11 Apr 2013 00:00:00 +0000
http://mixedbit.org/blog/2013/04/10/wwwhisper_add_on_update.html http://mixedbit.org/blog/2013/04/10/wwwhisper_add_on_update.html <![CDATA[wwwhisper add-on update]]> wwwhisper add-on update

The wwwhiser add-on is now in open beta. It can finally be enabled for any Rails or Rack based application that runs on Heroku! Give it a try. Because integration with the add-on is provided via Rack middleware, the cost is minimal; there is no need to write any code, few lines of config do the job.

]]>
Wed, 10 Apr 2013 00:00:00 +0000
http://mixedbit.org/blog/2013/04/10/underhanded_c_contest.html http://mixedbit.org/blog/2013/04/10/underhanded_c_contest.html <![CDATA[Underhanded C Contest]]> Underhanded C Contest

The Underhanded C Contest is in a way an opposite of the Obfuscated C Code Contest. The task is to write a readable and elegant piece of code that looks legitimate but hides a malicious feature. The feature should ideally look like an unfortunate mistake, not something intentionally added.

The contest is now back after four years of inactivity and the 2009 results are finally posted. Here is my runner-up 2009 entry.

]]>
Wed, 10 Apr 2013 00:00:00 +0000
http://mixedbit.org/blog/2013/03/20/wwwhisper_heroku_add_on_update.html http://mixedbit.org/blog/2013/03/20/wwwhisper_heroku_add_on_update.html <![CDATA[wwwhisper add-on update]]> wwwhisper add-on update

The wwwhiser Heroku add-on is now in a private beta phase. The official documentation is on Heroku. In this phase only Heroku beta users can officially sign up, but if you are interested in trying, drop me an email.

]]>
Wed, 20 Mar 2013 00:00:00 +0000
http://mixedbit.org/blog/2013/02/10/random_walk_illustrated_with_d3.html http://mixedbit.org/blog/2013/02/10/random_walk_illustrated_with_d3.html <![CDATA[Random walk illustrated with D3]]> Random walk illustrated with D3

I’m playing with D3 and I’m impressed how easy it is to create visualisations with this library. Below is my first attempt: a visualisation of a random walk (see the code). The mechanism is simple: each step of the walk is made either in the left or the right direction depending on a result of a coin flip. After several steps, a destination point is marked and a new walk is started.

If the simulation runs for some time, the destination points start to resemble the bell curve. Most points are near the centre, and it is very unlikely for any point to be at the edges. This is in line with a probability theory: for large number of walks, probability that a point is reached follows a normal distribution.

Some interesting facts about random walks:

  • During a random walk of an infinite length, each point is reached an infinite number of times.
  • During a random walk of an infinite length, a series of steps in one direction (for example left, left, left, left, …) of any finite length will be made an infinite number of times.
]]>
Sun, 10 Feb 2013 00:00:00 +0000
http://mixedbit.org/blog/2013/02/06/django_db_pool_on_heroku.html http://mixedbit.org/blog/2013/02/06/django_db_pool_on_heroku.html <![CDATA[Django DB Pool on Heroku]]> Django DB Pool on Heroku

Recently I switched my Django application that runs on Heroku to use production PostgreSQL database. According to the documentation common queries to such database should take less than 10ms. Unfortunately, Django requests were taking about 45ms (each request executed a single SQL query, with no joins and a single row result. The row lookup was done by an indexed column, and all data fit in the Postgres cache).

Just by enabling persistent DB connection with Django DB Pool, request processing time for the application was reduced to about 10ms! An application that uses dj_database_url (recommended by Heroku), can enable the pool with following settings:

DATABASES = {'default': dj_database_url.config()}
DATABASES['default']['ENGINE'] = 'dbpool.db.backends.postgresql_psycopg2'
DATABASES['default']['OPTIONS'] = {
    'MAX_CONNS': 1
}

Background

Django makes a separate connection to a database for each incoming request, this introduces a substantial (35ms in my case) per-request overhead for a connection establishment and authentication. In addition, Postgres maintains a query execution plan cache for each connection, if an application runs the same query for each request but over a different connection, benefits of the plan caching are lost, plans are recomputed each time.

There is a long debate about this issue in the Django community. The core team argues that connection management is not Django’s business and should be handled by an external connection pooler. I agree with this, but I also agree that the default and the only out-of-the-box option (close a connection after each request) is a bad choice. Django should keep the database connection open, and if this is not optimal in some setups, an external pooler can be used to close connections. Because Django closes a DB connection after each request, a pooler can only reduce an overhead of a pooler<->db connection setup, there is still per request overhead of a Django<->pooler connection setup, which can be cheaper, but is still suboptimal.

Running an external pooler only to have (somehow) persistent DB connections is also an additional burden. Fortunately, thanks to the Django DB Pool, this is unnecessary, and we can have fully persistent connections in Django!

]]>
Wed, 06 Feb 2013 00:00:00 +0000
http://mixedbit.org/blog/2013/01/31/wwwhisper_heroku_add_on.html http://mixedbit.org/blog/2013/01/31/wwwhisper_heroku_add_on.html <![CDATA[wwwhisper Heroku add-on]]> wwwhisper Heroku add-on

I’ve worked recently on making wwwhisper authorization available as a service for Heroku applications. This greatly simplifies wwwhisper setup and management. You can enable wwwhisper add-on for any Ruby based application with just 3 lines of config!

Following Heroku add-on development processes, the wwwhisper add-on is now in alpha testing phase and needs to be successfully used by several users before it can move to closed beta, then open beta and finally be officially released.

A grand plan is to offer a basic free plan and a fully-featured paid plan to cover the cost of infrastructure and support further development of wwwhisper.

]]>
Thu, 31 Jan 2013 00:00:00 +0000
http://mixedbit.org/blog/2012/12/16/c___puzzle.html http://mixedbit.org/blog/2012/12/16/c___puzzle.html <![CDATA[C++ puzzle]]> C++ puzzle

You have a C++ class with all methods marked const, no operator= and one private field x initialized by the constructor and not market mutable. Can a value of x change after an object is created?

For example, for a class below:

class Puzzle {
 public:
  Puzzle(int x_arg) : x(x_arg) {}

  int get_x() const {
    return x;
  }

  void someMethod1(...) const {
   ...
  }

  SomeReturnValue someMethod2(...) const {
   ...
  }

 private:
  // Disable default operator=
  Puzzle& operator=(const Puzzle&);

  int x;
};

Can x change and how?

The code must compile with no warnings. Rule out abuses like direct memory writes, incorrect casts or casting away constness like:

class Puzzle {
 public:
  ...

  void set_x(int new_x) const {
   // This is abusive and doesn't count.
   const_cast<Puzzle *>(this)->x = new_x;
  }

  ...
};

See the solution.

]]>
Sun, 16 Dec 2012 00:00:00 +0000
http://mixedbit.org/blog/2012/12/09/productivity_tip_of_the_week.html http://mixedbit.org/blog/2012/12/09/productivity_tip_of_the_week.html <![CDATA[Productivity Tip of the Week]]> Productivity Tip of the Week
sudo ifconfig eth0 down;
]]>
Sun, 09 Dec 2012 00:00:00 +0000
http://mixedbit.org/blog/2012/11/14/wwwhisper_on_openshift.html http://mixedbit.org/blog/2012/11/14/wwwhisper_on_openshift.html <![CDATA[wwwhisper on OpenShift]]> wwwhisper on OpenShift

I’ve prepared instructions and scripts to easily setup wwwhisper autorized nginx on the OpenShift platform. Please visit github for more details and links to a demo site.

]]>
Wed, 14 Nov 2012 00:00:00 +0000
http://mixedbit.org/blog/2012/11/05/sibling_domains_cookies_isolation_.html http://mixedbit.org/blog/2012/11/05/sibling_domains_cookies_isolation_.html <![CDATA[Sibling domains cookies isolation]]> Sibling domains cookies isolation

Recently I’ve spent some time studying the topic of cookies isolation for sibling domains. Relevant information is scattered through various resources, as a result, the issue is often misunderstood.

Sibling domains are subdomains that share a common suffix which is not a public suffix. For example, foo.example.com and evil.example.com are sibling domains, because example.com is not a public suffix; foo.co.uk and evil.co.uk are not sibling domains because co.uk is a public suffix. Mozilla maintains a list of public suffixes that is used by Firefox, Chrome and Opera. IE has its own list.

Browsers implement weaker isolation policy for sibling domains than for completely separate domains: a site can set cookies for its siblings.

Why subdomains?

Often subdomains are used by a single organization that controls all sibling sites. In such case danger is limited, because all sites are trusted. The sibling domains isolation policy was likely introduced to make life of such organizations easier. You can have login.your-company.com, that sets a session cookie for all *.you-company.com sites.

The problem arises when sibling domains are given to different, not necessarily trusted organizations. If a site that was given a subdomain can supply its own server-side or JavaScript code, it can set cookies for sibling sites that belong to others.

Professional sites almost always use dedicated domains. A dedicated domain is reasonably cheap and easy to setup, although still quite difficult for less tech-savvy people. Probably the biggest benefit of subdomains are shared SSL certificates. Certificates infrastructure was designed for online businesses and is way too hard to use for amateur sites. Yet, we store more and more sensitive things online that deserve a proper cryptographic protection. Having a subdomain from a provider that offers a shared SSL certificate is today the easiest way to have encrypted HTTP connections to the server.

Exploiting weaker isolation

The simplest trick, but usually of negligible impact, is for evil.example.com to set a cookie with a domain .example.com that has a name recognized by foo.example.com but an incorrect value. In case of session cookies this can cause foo.example.com user to be logged out - a minor annoyance.

A more serious attack can be executed against a site that uses not signed cookies as a storage space for user settings. Such cookies can be replaced by the evil sibling. For example:

Set-Cookie: MyTheme=BarbiePink; Domain=.example.com; Expires=Wed, 13-Jan-2021 22:23:01 GMT;

This can be easily avoided by either signing cookies with a server key or storing all setting on the server.

The truly dangerous attack is an equivalent of Login Cross Site Request Forgery. If evil.example.com obtained a legitimate session cookie for foo.example.com, it can set this cookie for some user of foo.example.com that visited evil.example.com, thus logging the user as someone else. As with the login CSRF, the impact of the attack depends on the nature of the site. For nonsensitive sites such as forums, wikis, blogs, the impact is minor, the user will be posting as someone else. For sites that store sensitive information such as online shops that store credit card numbers or a web search that stores private queries history, the successful attack can be fatal. The sensitive data can become exposed to the attacker.

Pseudo-protection

There is no elegant and reliable way for a sibling site to protect against the attack. Legacy RFC 2109 required browsers to send the domain attribute back to the server. This was a great idea that would enable web applications to distinguish cookies set by siblings. Unfortunately, it was never implemented.

A seemingly appealing solution, that could be employed in some scenarios, would be to configure a trusted reverse proxy that talks to all *.example.com sites to drop responses that try to set cookies with the domain attribute set to .example.com. This doesn’t work, because cookies can be also set from JavaScript.

Another idea is for foo.example.com to overwrite cookies that were set with domain .example.com. Unfortunately, returning:

Set-Cookie: sid=xyz; Domain=.example.com; Expires=Wed, 13-Jan-1990 22:23:01 GMT;

won’t do the job, because cookies are uniquely identified by a name, a domain and a path. So if evil.example.com sets a cookie:

Set-Cookie: sid=SomeValidSid; Domain=.example.com; Path=/auth; Expires=Wed, 13-Jan-2021 22:23:01 GMT;

foo.example.com needs to overwrite it with:

Set-Cookie: sid=xyz; Domain=.example.com; Path=/auth; Expires=Wed, 13-Jan-1990 22:23:01 GMT;

This means that with each response cookies returned by the server would need to cover all meaningful paths that the server uses. Very ugly and very impractical. The technique would still not fully prevent the attack, it would only make it more difficult. The sibling could still set cookies that would be included in some requests and then dropped.

Protection

An elegant solution, and the only that truly works today, is to request the top domain (example.com) to be added to the Public Suffix List maintained by Mozilla. Sites can not set cookies scoped to a domain name that is on the list. Many popular services that give subdomains to users are on the list (Google App Engine, Opera Unite, Red Hat OpenShift, DynDns). Adding a domain to the list is simple, there is no heavyweight process guarding it. Unfortunately, not all browsers use the list.

There is an idea to store information about public suffixes in DNS. This seems like a place where this information really belongs and is much more elegant than the centralized list. Hopefully we’ll see something like this in a future!

References

Tangled Web is a great book that deeply covers browser security.

A document that accurately describes how browsers handle cookies is RFC 6265. Earlier RFCs were wishlists, never fully implemented.

I’ve researched this topic while setting up a site on the Red Hat OpenShift platform. OpenShift gives HTTPS enabled subdomains to users (*.rhcloud.com addresses). It turned out the OpenShift crew was not aware of the cookie isolation problem, but they fixed the problem and added OpenShift to the Mozilla Public Suffix List.

]]>
Mon, 05 Nov 2012 00:00:00 +0000