Penetration testing & window.opener — XSS vectors part 1

Josh Graham
TSS - Trusted Security Services
6 min readDec 4, 2018

This is the first part of a four part series discussing security concepts related to the JavaScript opener variable (almost all the concepts can be applied to the top/parent variables too). This post will introduce the opener variable, how it relates to the same origin policy and discuss how the variable can be abused to achieve something close to a client-side open redirect. Part two will explore novel XSS vectors that build on the concepts discussed in this post. Part three will get confusing with some funky cross-window eval fun so that we can talk about a Universal XSS issue in part four.

This blog series is a follow up from my 2018 CrikeyCon talk https://www.youtube.com/watch?v=veXDFx2qJBk. This blog series will dive deeper into the technical topics touched on in the video.

Introducing the window.opener variable

JavaScript contains an open function that is used to open a URL in a pop-up window. This function is often abused by dodgy websites to open annoying or malicious ‘advertisements’ but also has legitimate uses. When you call the open function, the browser automatically populates a variable in the new window that points back to the window that opened it. The variable is aptly named ‘opener’.

One of the legitimate uses for the opener variable is to enable single sign-on functionality without navigating away from the current page. The process looks a little like this:

A couple of interesting things to note in the animation above.

  1. The opener variable is set when the pop-up is on the ‘singlesignonprovider’ domain and persists even after a new page is loaded (‘https://singlesignonprov…’ -> ‘http://xss.vg?token=abc…’). This is different to most JavaScript variables that do not persist when a window loads a new page.
  2. The single sign-on provider redirects back to the original domain before utilising the opener variable to communicate with the original window.

Why doesn’t the single sign-on provider simply use the opener variable to send the logon token directly to the original window using opener.token = …? Because that would violate the same origin policy.

The same origin policy and pop-up windows

To utilise the opener variable to make changes in another window, both windows have to satisfy the following three checks.

  1. Same domain (e.g. tsscyber.com.au)
  2. Same URL scheme (e.g. HTTP, HTTPS, FTP etc)
  3. Same port (usually 80 for HTTP and 443 for HTTPS)

If you pass these three checks then you pass the same origin policy and a browser will allow JavaScript to execute between the windows (the same check applies to JavaScript interactions between iframes or other embedded content as well).

For example, let’s say you’re a bad guy and you setup a malicious website (http://xss.vg:80 in this example) that, when someone visits the site, JavaScript will open a pop-up window and then load the victim’s gmail account into the opener. Your malicious JavaScript tries to read the contents of the victim’s emails using opener.document.body.innerHTMLor something similar.

Unfortunately for you as a bad guy, using the opener variable to read data in the gmail window violates all three of the same origin policies and the browser’s security controls will prevent the unauthorised access :-(

Same origin check example

http://xss.vg:80 VS https://mail.google.com:443

  1. Domain: xss.vg != mail.google.com
  2. URL Scheme: HTTP != HTTPS
  3. Port: 80 != 443

So if it’s impossible for the opener variable to be abused to run JavaScript in a cross-domain window (which is what XSS actually is) then what can it do?

‘Safe’ cross-domain operations

Although it is obviously a security issue if you can read/modify a cross-domain window, there are a couple of functions and variables that browsers expose even when the same origin checks fail. Below is the list of safe cross-domain operation/variables that I’m aware of:

  1. blur — shifts focus away from the window
  2. close — closes the window (but only if it was opened by JavaScript)
  3. closed — indicates if the windows was closed
  4. focus — focuses the window (but doesn’t always focus the window…)
  5. frames — same as opener
  6. length — the number of iframes in the opener
  7. location — write-only access to the opener’s URL
  8. opener — the opener’s opener (or null if it doesn’t have one)
  9. parent — gets the opener’s parent window (which might be itself)
  10. postMessage — the safe way to do cross origin communication
  11. self — I’m not entirely sure on the usage of this one. Is apparently useful for web workers
  12. top — the opener’s top window (which might be itself)

Of these 12 safe cross-domain operations/variables, number 7: ‘location’ and the ability to access the opener’s iframes is of particular interest.

Messing with cross-origin locations

The first potential issue that comes to mind with having write access to the opener’s location value is JavaScript and Data URIs. In our example above the following commands achieve the same result:

opener.secretValue

opener.location = 'javascript:window.secretValue' //note the use of window.secretValue here as opposed to opener.secretValue as the goal is for the JavaScript to run in the context of the opener's window

Fortunately, browsers have already thought about this vector and it results in a security error. Equivalent functionality using a data URI might look like this:

opener.location = 'data:text/html,<script>window.secretValue</script>'

But there are two problems here. First, the data URI scheme causes the opener’s window to reload, which resets all user defined variables to undefined (i.e. window.secretValue is lost) and changes the domain context of the window to that of the pop-up window’s domain (this is opposed to a JavaScript URI which doesn’t always cause the window to reload/change context). Second, most browsers now disallow top windows from being navigated to a data URI. If you try this navigation in Chrome it will result in nothing happening (an error message is printed to the console) and in Firefox it causes the window to close (might be a bug…).

You are still allowed to use data URIs in embedded objects (such as an iframe). Since we have access to opener.length we can know if our opening window contains any iframes. To access an iframe in the opener you can index into it (like you would an array) i.e.var openersFirstIframe = opener[0] would access the first iframe in the opener’s window. However, attempting to set an iframe’s location cross domain is denied by browser security rules unless you are its opener or its parent.

Even though browsers have blocked the ability to utilise the location variable for XSS, you are still able to navigate the opener to any domain you like (i.e. opener.location = 'http://xss.vg' ). This equates to a sort-of client-side open redirect where a malicious website can ‘hijack’ the navigation of another window (or tab) by forcing the user to visit a particular website (interestingly, at the time of writing this, Chrome has blocked the ability to change the location of the opener after a user navigates to another page. This is a step in the right direction in my opinion).

I’ve seen this functionality used in a semi-legitimate way to circumvent popup blockers. I’m guessing here, but it looks like pop-up blockers have some heuristics about whether a pop-up window is legitimate or spam so a website will load itself into the pop-up window and navigate the original window to an advertising website. This tricks the pop-up blocker as the pop-up doesn’t contain the spam, the original site does. Pretty smart.

The story so far

So far we have discussed pop-up windows and the opener variable. We have seen that browsers have implemented security controls that disallow JavaScript execution to occur between windows based on the same-origin policy checks. Apart from a poor person’s client side open redirect issue, the opener variable seems completely safe from nefarious uses. Join me in part two to extend our knowledge of how we can achieve XSS using some novel vectors.

Josh is a senior penetration tester at TSS specialising in web application penetration testing.

TSS is a specialist cyber security company providing penetration testing, security assurance consulting and managed security services. More information is available at our website https://www.tsscyber.com.au.

--

--