I’ve been recently reading Michal Zalewski’s “The Tangled Web”, a book which tries to map the whole security landscape around browsers and Web applications in about 300 pages… it does a pretty good job 🙂

Now, in Chapter 9, he talks about “Content Isolation Logic” and in one specific section he touches on the document.domain property of the DOM of a page. So, in short, when two pages, foo.example.com and bar.example.com want to communicate through JavaScript, by default they cannot since the Same Origin Policy allows accesses only when the protocol, domain and port fully match. Since, “foo.example.com” !== “bar.example.com” the check fails and thus the domains can’t communicate. Since this is somewhat too rigid, a developer can choose to relax the domain of his page from foo.example.com to example.com. In JavaScript, this is a simple assignment to the document.domain property:

document.domain = "example.com";

If both pages first do the same relaxation and then attempt to communicate (e.g. read each others’ cookies, read content in the DOM and add/remove nodes) all is well. Now obviously “foo.example.com” can’t relax its domain to “www.google.com“. The new relaxed domain must be a higher-level domain from where it already is. For the same reason, the page “foo.example.com” can’t relax its domain to “foo.foo.example.com” since that is not really relaxing 🙂

Relaxing more than you should

Now, as a web developer you have about a thousand ways in which you can screw up the security of your site. One of them is to relax your domain too much. What would happen if “foo.example.com” relaxes its domain to “com“? That is a valid higher-level domain after all. Well… there is no good reason for a person to do that, other than a severe misunderstanding of how the whole thing works. For this reason, all modern browsers disallow the setting of document.domain to a TLD value… well, all except Chrome that is.

I decided to make a simple experiment. I resolved locally two domains, myattacker.com and myvictim.com. myattacker.com first relaxed its domain to “com” and then loaded myvictim.com in an iframe which also wrongly relaxes it’s domain to “com”. A short script in myattacker.com attempts to reach in the iframe and read the HTML of the body of the loaded page, containing a secret value. If it succeeds, it dumps the contents of that iframe in its own page.

Testing the browsers

So, we start with Firefox, version 10. Here is a screenshot of the page:

As it should be, Firefox says no and the earth keeps spinning. How about Internet Explorer, the usual black sheep of the web?

Who would have thought? Internet Explorer 8 doesn’t like the thing…

Opera?

How about Google Chrome (version 18.0.1025.108 beta), the source of all sunny, funny and nice?

Aha! And that’s how the cookie crumbles. Chrome says “sure, go on” and the attacker reaches in the victim’s iframe. Game over! This behavior can also be abused to hack a site that’s vulnerable to XSS. Instead of injecting the full malicious payload, you simply relax its domain and then proceed to inject code which will never be visible at the server-side of the vulnerable Web application, thus making detection and mitigation much harder. More on that, here.

Closing words

Now why is that, you may ask? These guys implement process per tab, client-side xss filters and what not, to protect the user from harm, even if it is the fault of a specific website. Short answer, I don’t know. The argument that changing it is hard, seems a bit flimsy to me, especially given Google’s prior engineering track record.

Till next time

Nick Nikiforakis