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