This article discusses a new bug class that was introduced in XMLHttpRequest Level 2, how the bug manifests and some solutions to different problems.
HTML5 is a hot topic in web security today. Mostly because of how expansive it is in relation to older web specifications, but also because it introduces new concepts for web developers to misunderstand.
First, some concepts.
Web 2.0 Design Trends have been changing drastically over the past several years. I make no claims to be any kind of web designer, but I do know how to develop code for the web and I am able to make simple observations. When you go to a bunch of websites and you see similar functionality on all of them, that’s called a trend. Here’s one specific trend I observed over the past several months.
Facebook and Twitter have started using URLs that look like this:
/HockeyInJune/lists/memberships; the part of the URL that directly follows the
Technical Note: This functionality has since been removed from these websites because of the security issues we will discuss below.
Same Origin Policy (SOP) dictates that client-side scripts are confined to their originating site, this means that a script from one site cannot influence any other objects belonging to another. SOP has been implemented in modern browsers for a long time, but SOP was first standardized in XMLHttpRequest Level 1. XMLHttpRequest Level 1 dictates that XMLHttpRequest() follows SOP.
HTML5’s new functionality.
Cross-Origin Resource Sharing (CORS) is new functionality of XMLHttpRequest Level 2 that allows XMLHttpRequest() to make cross-origin requests, directly violating XMLHttpRequest Level 1’s SOP restriction. This is done for added functionality, so developers can asynchronously pull content from other domains. The specification dictates that Cross-Origin Resource Sharing checks the remote host to see if it allows cross-origin requests, and this is going to be the root cause of the security issues introduced.
Now, because scripts are allowed to interact cross-domain, we need to redefine Same Origin Policy.
Scripts are confined to their originating site
Documents are confined to their originating site
What do we mean by Documents? Every DOM object has a parent Document object. Scripts in one Document object, cannot interact with scripts from another Document object. This sounds good, but where is this line drawn?
This page is located at safe.com
In the example above, the script that originated at
malicious.com is in
bad.js can affect elements on the page at
safe.com. The specification expects the developer to make sure that only legitimate parties have access to the Document object. You be may inclined to immediately jump to the conclusion that sanitizing user input will fix this situation. Instead of assuming the example above was caused by a Cross-Site Scripting vulnerability, you’ll get a better understanding of the possibilities if you consider that it was caused by Malicious Advertisements.
So, you might be thinking that Same Origin Policy isn’t doing such a great job anymore. But SOP still works well for
Cookies, which are owned by their domain’s own Document object.
Cross-Origin Resource Sharing
Let’s take a look at our demonstration website.
Technical Note: This demonstration has been tested in Google Chrome, but should work in any browser that implements this part of the HTML5 specification correctly.
This website implements a number of AJAX features including Cross-Origin Resource Sharing. Each button will append a different string to the end of the URL, and reload the page. When the page is reloaded the string after the
#! in the URL will be passed to XMLHttpRequest.open().
Go Web 2.0! button will request a benign same-origin page. This is expected functionality.
Go Semi-Malicious! button will request a malicious same-origin page. This is expected functionality.
Go Cross-Origin! button will request a benign cross-origin page. This is new, previously unexpected functionality.
Cross-Origin Resource Inclusion
To exploit this bug, replace a malicious page URL after the
This is index.php at http://126.96.36.199/cori/
All we need to allow the vulnerable website to make a cross-origin request to our malicious page is set one response header;
Access-Control-Allow-Origin: *. Note that this is set on the cross-origin site that we control. When you navigate to the URL above, you can see that the malicious page has control over the user’s cookie at
Here is where an attacker has control over the url parameter in XMLHttpRequest.open().
Now, if you’ve been paying very close attention, you’ll notice there was a tiny bit of hand-waving going on. There are a few technical details about Document ownership that we didn’t discuss about that were vital to our exploit.
Passing DOM objects through innerHTML changes the parent Document object of those objects. There is an internal ownerDocument property that every DOM object has that is only accessible by the browser. This property defines which parent Document object a DOM object belongs to. When a DOM object is passed through innerHTML of an object belonging to a different parent Document object, its ownerDocument property is changed to match its new parent DOM object’s parent Document object.
A Note About Cookie Snatching
An attacker was able to steal
isis.poly.edu’s cookie because the malicious remote resource at
http://188.8.131.52/cori/ was injected into
isis.poly.edu’s DOM tree. Cookies were not sent cross-origin in this demonstration. Although, CORS does allow for that functionality for cross-origin authentication by setting a request header;
Find all calls to open() on XMLHttpRequest() objects via static or dynamic analysis and see if the the url parameter can be tampered with. If it’s a hard-coded string, you’re out of luck.
Prevent all cross-origin requests by setting the
Up until now, we’ve been blaming programmers for these bugs, but it’s possible we can shift some of the blame to the specification writers.
The most obvious issue is the site that makes the cross-origin request never explicitly allows for this type of thing to happen. Like we’ve seen with Adobe’s
crossdomain.xml, if the originating site is allowed to choose which domains cross-origin requests are allowed to be made to, there is less chance of a website being vulnerable to Cross-Origin Resource Inclusion. This can be specified in a header just like how the remote site verifies that a cross-origin request can be made to it. But, we don’t want to swap the authoritative server in this relationship because there may be reasons that the remote site needs to verify that cross-origin requests can be made to it. However, it’s not infeasible to have both the requester and the requested sites verify that cross-origin requests are allowed to each other’s domains. This would effectively mitigate this vulnerability.
If that isn’t possible, the specification could require all browsers to treat an absence of the
Access-Control-Allow-Origin header as if it were set to
null, meaning that no cross-origin requests would be allowed from any website by default.
If that’s too restrictive, the specification could add clauses to not allow potentially dangerous DOM objects (
script objects, for instance) to become children of a Document object with a different domain via
innerHTML or other means.