Understanding javascript SOP

Javascript security mechanism relies on the "Same Origin Policy".

In a nutshell, that means that javascript code is executed in a "context" defined by the domain on which the embedding html page is hosted. Code running in a specific context (say, "mydomain.com") can't access the window object of another context (say, "otherdomain.com"). This is specially true for iframes: simply because your web page includes an iframe that points to google.com doesn't mean you can execute script in google browsing context (put otherwise: you can't steal a gmail cookie from someone visiting your page).

Some people wrongly concludes from SOP that you can't add in your page a script tag pointing to a script on another domain. You can. And the script from there will be executed within your page security context. Which means the script from there can access your users information. Which means you should better trust it on your life...

Why sandbox code after all?

The reason is simple: a script you can't trust which is executed inside the security context of your web page can do whatever it wants.

For example (and not limited to):

  • change the page content
  • steal your users cookies (and unfortunately cookies usually convey a not-changing-much token giving full access to the owner personal informations, but that's another rant)
  • inject the global execution scope with specially crafted code to snoop over your application mechanisms and steal valuable pieces of data (credit card numbers comes to mind)
The idea pretty much is that in javascript, your trust in the global scope should better be rather limited. All-in-all, you're stuck with literals. Constructors (including Array, String, Object) can be spoofed. "Native" methods (sort, replace) can be spoofed. "Native" objects (XHR) can be spoofed.

So, if you think that a private scope using closures is enough to hide your precious information, you're just asking to be kicked.

The ultimate truth being: there is no way to protect your code or data once third party code is evaluated in the same scope as you.

The scenario

The easiest way to obtain a sandbox obviously is to put the third party scripts on another host along with an embedding html file, and load the latter in an iframe. Applying SOP.
Now, that's hardly flexible, and obviously requires server work.
So, what we target (and will fail to obtain, so, don't hold your breath) is the ability to evaluate a string from within our own security context, in a secure way.

Bad idea, number one: using iframes and data urls

The first idea that comes to mind is to stuff the third-party script into a data url, and use that as the source of a (possibly hidden) iframe. That way (or so you expect), the injected payload will be executed in a different context without access to your scope (as you would assume from a scheme change).

That kind of works actually, but in gecko. Ya. The injected script will gain access in firefox. That point made a lot of noise actually, as some self-called "security researchers" were thinking they found a major hole

For more information, you may read this blog post and surrounding informations. Additionally, there exist a number of reports on bugzilla.

As a matter of fact, this bears a lesson for server code developers, as injecting data urls is rather trivial on a large number of websites out there: you should never ever blacklist protocols when allowing users to post links. Rather parse urls correctly (according to the specification), and use a whitelist of allowed schemes.

Bad idea, number two: scope overload

So, that didn't work...
What about a scope-based approach then?

So, you would go for something like that:

new (function(window){
  with(window){
    eval(thirdPartyCode);
  }
})( sandbox );

And you would of course control strictly what you expose in the sandbox. Probably something like that as a balls-squeezing starter measure:

var sandbox = {};
for(var i in window)
  sandbox[i] = null;


Well, that sure sounds good.

Now... go read the previous post on this blog, and realize that function statements (in Webkit, Opera and IE) are bound to top-level code and just bypass your nifty little trick. Too bad. That still leaves firefox (and that leaves you dreaming there was nothing else on the web) - as that last point doesn't apply in Gecko. Now, go read devmo, and check-out the fine "magical-gecko-only" __parent__ property:
  • lets you climb the scope chain
  • impossible to delete
  • impossible to override
Game over :-)

Conclusion

Believe someone who did: don't loose your time on this quest, and don't believe people who say the javascript execution sandbox graal exists.
Past the chainsaw massacre approach (a.k.a. Google Caja), there is no way to be safe when sharing execution context with third party javascript code.

And if you're already sharing scope with third-party scripts, as Yoric puts it "Be Afraid, Be Very Afraid" :-)