whatwg / html

HTML Standard
https://html.spec.whatwg.org/multipage/
Other
8.13k stars 2.67k forks source link

WindowProxy exposes cross-origin side channel #4005

Open jakemco opened 6 years ago

jakemco commented 6 years ago

window.frames can reveal information about sites in a different origin, providing a side channel to break the same origin policy.

Description

Given two sites, site A and site B. Assume site B has a page where it renders an iframe for each instance of some content. For the purposes of this example, that iframe can be to another page on site B or to a third site. Site A can render a pop-up or an iframe to site B, retrieve a handle to the window, and get back the number of frames via otherWindow.frames.length. The number of pieces of content for a particular user on this page could be sensitive (comments, messages, search results, etc.), or even the existence of frames on the page could be a side channel (an iframe only rendered on a particular page for users with a specific role on that site), and so this api is therefore exposing a cross-origin side channel for that information.

Site A is also able to change the location of the iframes rendered in site B by calling otherWindow.frames[n].location.replace('...'). In this way, an attacker could change the content of a displayed iframe to display alternate information, or change the target of a * target origin postMessage (which is problematic to begin with, but closing this hole closes that hole).

Example:

This example uses iframes because it was quick to write, but it could just as easily use pop-ups (open a link with target _blank and get a window handle) to bypass XFO.

site-a/index.html:

<!doctype html>
<html>
  <head>
    <title>Site A</title>
  </head>
  <body>
    <iframe id="target" src="http://localhost:8081/?n=6"></iframe>
    <script>

// give the other window a second to load
setTimeout(() => {
  var w = document.getElementById('target').contentWindow
  var child = document.createElement('b');
  child.innerHTML = w.frames.length;
  document.body.appendChild(child);
}, 1000);
    </script>
  </body>
</html>

site-b/index.html:

<!doctype html>
<html>
  <head>
    <title>Site B</title>
  </head>
  <body>
    <script>
var urlParams = new URLSearchParams(window.location.search);
var n = urlParams.has('n') ? parseInt(urlParams.get('n'), 10) : 1;
for(var i = 0; i < n; ++i) {
  var child = document.createElement('iframe');
  child.src = 'https://www.facebook.com/plugins/like.php?href=https://github.com/whatwg/html';
  child.height = '20';
  document.body.appendChild(child);
}
    </script>
  </body>
</html>

running site-a as localhost:8080 and site-b as localhost:8081, eg: python3 -m http.server 808{0,1}

In this example, n is clearly not sensitive as it's a url parameter that site-a chose, but it could just as easily be based on a cookie value or other sensitive data that site-a should not be able to retrieve.

In the js console of site-a you can also do document.getElementById('target').contentWindow.frames[0].location.replace('http://some-other-site') to manipulate the iframe from another origin.

Note: this also works with window.length, window[0], etc.

Suggested Solution

Either prevent sites from accessing cross-origin frames at all, or limit them to accessing cross-origin frames that point back into their own origin, eg: a site-b with two iframes, one that points to site-b and one that points to site-a would return a window.frames.length of 1 when called from site-a.

rniwa commented 6 years ago

See https://github.com/whatwg/html/issues/3740 for a proposal to limit this to some extent.

annevk commented 6 years ago

See also #1509 for a similar issue.

domenic commented 6 years ago

For anyone else momentarily confused, remember that window.frames === window, so this is not something frames-related, it's just how windows work.