On the security implications of window.opener.location.replace()

It’s no secret I am a big fan of many HackerOne bug reports and public penetration test reports authored by companies such as Cure53 and Least Authority.

In fact, pretty much every week I spend some of my free time reading bug reports. Regularly I stumble upon very interesting attack vectors and oftentimes learn tricks I had never seen before. This post is about one of the techniques I learned sometime ago whilst reading a report submited to HackerOne, authored by a bounty hunter named Daniel Tomescu.

After finished reading the particular report, I was a bit confused and not convinced this was a bug in the application at all. Then I had a discussion about it with my friend Shubham Shah and he clarified some of my doubts about it.

Digging in a bit more about the issue, I discovered this is indeed a design decision of all major browsers. However, it seems to me that the security ramifications of this design decision are not well understood and often ignored.

The objective of this post is to present the issue, discuss it in as many details as possible and make a case of why this issue has a greater security impact than many people may think.

Introducing window.opener

When a window opens another, the child window contains a property known as opener that serves as a reference to the parent window. Using this property a child window can access the main window, provided this does not violate the security restrictions enforced by Same-Origin Policy.

An opener contain the same methods as other windows, including location.replace(), a method used to navigate windows. As the name suggests it replaces the current location with a new URL, regardless whether or not both windows are same-origin. For more information about this method see this W3Schools link.

This behaviour may introduce unintended security risks that are often overlooked.

A practical example

In the following example we try to simulate the scenario where a user clicks on a link that leads to a page that will replace its opener. Tested with Chrome 40.0.2214.115 and Firefox 36.0.1.

Test link:

<html>
<a href="http://example.com/target.html" target="_blank">Click here</a>
</html>

Target link (that will perform the actual replace):

<html>
<h1>Test!</h1>
<script>
window.opener.location.replace("http://www.whatever.io");
</script>
</html>

Despite the fact in the example we have two different HTML pages, there is no need for an adversary to provide a page per se; all it takes is a link to the malicious page.

Example with Facebook

Wait, but isn’t Same-Origin Policy supposed to protect me from this attack?

Long story short, the answer is no. The general idea behind Same-Origin Policy is that scripts from different origin running in a frame or a tab cannot manipulate content in another. By manipulating content it means it cannot read elements from the DOM of another tab/frame or write into them.

It is not hard to see how this the cornerstone of modern web’s security model. SOP is an extremely useful feature that serves as a very strong security boundary to isolate frames within websites. If you are unfamiliar with this concept, Wikipedia has a decent introduction to the subject.

This issue, on the other hand, seems to stem from another, little-known browser policy: Frame Navigation Policies. This is the policy that dictates how a frame can navigate another, including navigating it to a different URL unrelated to the origin.

Frame Navigation Policies

Navigation policies come in a variety of flavours and, unsurprisingly, their implementation may vary depending on the browser. Older browsers like Internet Explorer 6 implement a very permissive policy, where any frame from any origin can navigate every other frame within the browser.

In other words, any page hosted by attacker.com can navigate frames within confidential.com. This attack was known as “Cross-Window Attack” and was initially published by the almighty Georgi Guninski in 1999.

In response to Cross-Window attacks, other slightly more locked down policies were implemented but they also had their own shortcomings. For example, one of the proposed policy could mitigate Cross-Window attacks but if the frame was loaded from the same window (a common practice in the world of web 2.0), it was still able to navigate the top frame.

Other stricter policies are Descendant Policy, where a window can navigate only its descendants (not to be confused with the Southern California punk rock band Descendents), and Child Policy, where a frame can navigate only its direct children.

For more information about navigation policies, refer to this paper from 2008 authored by security researchers from Stanford University.

HTML5 security specification related to frame navigation

Frankly, after reviewing the paper by Stanford I still could not figure out which navigation policy is currently implemented in the browsers I was testing this issue. Definitely not the permissive one, but from my understanding of the policies it seemed like Descendent and Child were not strictly adhered to either.

To add more trouble to the mix, the security specs document of HTML5 is full of very confusing wording.

Quoting the commentary 12 of ticket #168988:

“For the record, it looks like the spec’s wording has changed and now allows cross-origin openers to be navigated. Here’s an updated quote from https://html.spec.whatwg.org/#allowed-to-navigate: “A browsing context A is allowed to navigate a second browsing context B if the following algorithm terminates positively: If A is not the same browsing context as B, and A is not one of the ancestor browsing contexts of B, and B is not a top-level browsing context, and A’s active document’s active sandboxing flag set has its sandboxed navigation browsing context flag set, then abort these steps negatively. Otherwise, if B is a top-level browsing context, and is one of the ancestor browsing contexts of A, and A’s node document’s active sandboxing flag set has its sandboxed top-level navigation browsing context flag set, then abort these steps negatively. Otherwise, if B is a top-level browsing context, and is neither A nor one of the ancestor browsing contexts of A, and A’s Document’s active sandboxing flag set has its sandboxed navigation browsing context flag set, and A is not the one permitted sandboxed navigator of B, then abort these steps negatively. Otherwise, terminate positively!” The spec now defaults to allowing the navigation, which seems to be consistent with existing browser behavior. I’m not opposed to seeing that changed, but someone would have to find the time to measure the compatibility impact. “

According to the comment cross-origin openers can be navigated by the child. This is why the above exploit works.

Why I think this issue is dangerous

Reading tickets from Chrome, it seems like overall consensus of Chromium developers is that the issue with window.opener.location.replace() is not a big deal — it is even marked as WONT_FIX in one of the tickets.

Quoting the 5th comment of ticket #429395:

“Given this, I don’t see any risk to users more than the users just clicking on a link and visiting a new page, so I am closing with WontFix.”

I personally disagree with this view. While I understand the reasoning that users should be aware of the inherent risk of clicking on a link and that there’s no impact to the UI indicators, such as URL bar, of the navigated site, even the most tech-savvy user will never expect the original tab to be redirected elsewhere. In my opinion this dramatically increases the likelihood of successful phishing attacks.

How to fix

What I believe is the easiest to implement of all proposed fixes for this issue can be found on this Mozilla ticket and this Chromium ticket.

It is possible to disassociate browsing contexts by opening anonymous windows and setting their window.opener property to null. In HTML5 supported browsers when using the anchor tag and “_blank”, by adding “rel=noreferrer” it automatically destroys the ‘opener’ property of the new tab. The following example achieves this and solves the issue without much effort (tested with the latest Chrome):

<html>
<a href="http://www.whatever.io/whatever-replace.html" target="_blank" rel="noreferrer">Click here</a>
</html>

Conclusion

This particular decision involving navigation policies exposes web browsers to a greater risk than many might think. I personally would like to see these policies locked down a bit further. It might break some websites that rely on this weird functionality that probably should not even be there in the first place.

The benefits of a more secure browsing experience, in my opinion, outweigh the potential annoyances it may cause to web developers.

Luckily, from a web application developer perspective, mitigating the issue does not require a massive effort and can be as simple as adding a “rel=noreferrer” attribute to an anchor tag.

Advertisements
On the security implications of window.opener.location.replace()

3 thoughts on “On the security implications of window.opener.location.replace()

  1. Required says:

    Who is Mario? As in the brother? And why is a javascript fix mucking with the prototype of the page better than just turning the referrer off? It’s a third party page, I don’t trust it, I don’t want them knowing about me.

    Like

  2. Hi, it is my opinion that the rel=”noopener” solution is not sufficient. It is difficult to apply to archived pages and easy to miss when applying to dynamic pages. For example, I wrote an output filter to apply the rel tag to all my web application pages when sent to the requesting client, and it worked – until Ajax calls in the page created links. I had to then look for everywhere where Ajax modified the DOM and if links were involved, fix them.

    A much simpler solution would be an HTTP header. Preferably window.opener() will not work if the window with the link wasn’t sent with a header allowing it. I doubt that will get through, so as an alternative, an HTTP header that tells the browser on the page served with the header, window.opener() will be disabled for all links.

    For some reason it seems that the WhatWG specification people at Github (lot of which are Google employees) don’t want a proper fix. I know that OAuth depends on the current unsafe behavior, and it wouldn’t surprise me if a lot of the various Google apps also depend upon the current unsafe behavior.

    I hope this whole gets plugged, it is very dangerous – and rel=”noopener” is not the right solution, too difficult to consistently apply everywhere in a web application. Headers are easy.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s