SOP bypass / UXSS – Adventures in a Domainless World (Edge)

Today we are going to walk around a few design issues that, when used together, will end up in a full SOP bypass or Universal Cross Site Scripting (UXSS) on Microsoft Edge. If you are not a security researcher but you still want to understand this vulnerability, think about it this way: visiting a malicious webpage an attacker will be able to read the cookies of your fav sites, change the content on the fly (on the client), sometimes even get personal information like saved usernames/passwords. Also, because Microsoft Edge uses protected internal resources to do special things, an attacker can potentially access those resources and probably set Edge configuration flags, open IE, etc.

Here we have a video exposing bing cookies and another video showing the content of nature.com. But keep in mind that those sites have no problems at all: the vulnerability is exclusively from Microsoft Edge browser. Let’s see how it’s done!

Domainless World

The about:blank is a very special URL that sometimes gets confused and it does not know where it belongs to. Think about it: if we are in www.magicmac.com/dom/index.html the document.domain is obviously www.magicmac.com, but what would be the domain of about:blank? It depends. In theory it should match the domain of its referrer, the webpage that set its URL. For example, if we are in www.magicmac.com and we click on an about:blank link, then, this particular about:blank will have www.magicmac.com as its domain.

Another example could be an iframe with its source pointing to about:blank explicitly, or without a source at all (the browser defaults it to about:blank).

So, an about:blank loaded from goodfellas.com looks similar to one from evil.com because both URLs are the same, but they have a different document.domain so they cannot access each other.

But let me ask you a question: what would be the domain of an about:blank that we’ve manually typed into the address bar? Got you! The answer is important, so I’ll zoom-in DevTools a bit.

It’s empty and it has a tremendous power as we will see in a bit, but just to be sure we are on the same page, let’s call URLs without a domain “domainless” and “domained” to the ones that have a document.domain.

Bug hunter, what follows is the most important thing you will read in this post.

“A domainless about:blank is capable of accessing any domained about:blank”

In other words, a domainless about:blank can access a domained about:blank without restrictions. Let’s cheat here for a moment and quickly add a bing.com iframe into this page, straight from the debug console.

document.body.innerHTML = '<iframe src="http://www.bing.com/images/search?q=microsoft+edge"></iframe>'

Great! We have now a framed bing.com inside a top domainless blank, but our goal is to find a blank iframe inside bing because as we said, a domainless blank (main window here) will be able to access any domained blank (a blank iframe inside bing.com)

In this case it will be easy because we are using bing.com already has blank iframes. But let’s get the taste of it anyway! Even from the debugger, the next instruction would normally return an access denied, but because the top is domainless, it will work. Let see!

window[0][0].location.href = "javascript:alert(parent.document.domain)";

Bang! I know this is not impressing you because we are working from DevTools, right? But for me, it’s the most important thing that we’ve done because if we grasp this concept, then, finding new UXSSs will be to some extent, easy. From now on, every time we find a way to access a domainless blank (generally about:blank, but we can use others as well), we will have a UXSS. We are working with DevTools because I want to make sure that we completely understand what we are doing, but of course we don’t need it!

Stand-Alone PoC. No DevTools Required

Let’s do it for real now. We need to find a way to create a domainless site accessible from a regular webpage, and the quicker way to do it is with a data: URI instead of an about:blank. Same thing, just a different protocol. However, if we load a data: uri inside an iframe, its domain will be the same as its referrer domain (just as we’ve seen at the beginning with the about:blank),  and if we attempt to load a data: uri on top, Edge will refuse sending us to an error page.

However, there are several tricks that we can do to get our domainless data: uri, and today we are going to explore the Flash version because it is extremely simple. In fact, I’ve been using this flash since 2005 without changes, it only sets a URL from its query-string. Grab it an use it!

<iframe src="geturl.swf?target=_top&redir=data:,[html/script goes here]"></iframe>

See? Just add the URL you want to load in the redir param and it’s done. In this case we are using a data: uri which ends up being loaded in the top, domainless. Also, to fool Edge we need to load the swf inside an iframe, otherwise it won’t work (error page). Try it and see for yourself!

By the way, don’t forget that we can find alternatives to achieve the same thing. We just happen to use this because it’s the first that we found, and Adobe guys will probably blacklist the data: uri helping my friends @Edge to get rid of this bug. However, there are many ways to achieve the same thing without the flash file. Make the idea yours, and find your way!

OK, now that we are in a domainless window we can inject an iframe pointing to bing.com, but Edge is in a state that doesn’t render elements correctly. If we try to use createElement/insertAdjacentHtml/etc it will fail. I mean, Edge will draw a dead iframe just like a car without an engine: it simply does not work. To overcome this problem we will document.write ourselves, forcing the browser to render everything again. And because we are in a domainless URL, the document.write will keep us exactly in the same location/domain.

document.write('<iframe src="http://www.bing.com/images"></iframe>');

Perfect! Now we can access bing’s blank iframe, but remember we’ve been lucky because not all sites will have “free blank iframes” inside.

window[0][0].location.href = "javascript:alert(parent.document.cookie)";

[ Click here to see the PoC ]

Update: [ Patched – CVE-2017-0002 ]

I’m getting excited, this is working without DevTools! Oh no, I know what you are thinking now, skeptic bug hunter: Bing is serving us a couple of blank iframes in a silver platter, so this was easy! And you are right, but I was just celebrating a bit! From now on I will call you killjoy! No more “bug hunter”. 🙂

Let’s continue, killjoy. I know that sites won’t like the idea of placing blank iframes for us, so we need to find our way.

Owning non-cooperative sites

Imagine that we are in the second step where the top is a domainless data: and our iframe is correctly rendered but pointing to nature.com instead of bing.com (because nature has a non-blank iframe inside). If we try to change the location of that iframe Edge will refuse and open a new window instead. In other words, doing something like this won’t help.

// We are inside a domainless data: so Edge will open a new
// window instead of changing nature-iframe's location
window[0][0].location.href = "about:blank";

That won’t work. Maybe it can be bypassed but I haven’t tried enough. It’s a problem that happens in this domainless situation, so, we can just open a new window with a real URL and work from there. This is exactly what we will do:

  1. Open a new window with a framed nature.com inside.
    [ NOW WE ARE INSIDE THE NEW WINDOW WITH A REGULAR URL ]
  2. Change nature’s inner-iframe location to about:blank so we can give it a name. Yes, we want the iframe to have a name.
  3. Set a name to the about:blank iframe so our domainless opener can access it via window.open. Don’t forget that we are right now inside a window with a regular URL, it’s our opener who has the power. We will name this iframe just like this: window.name = “DAVID_COPPERFIELD” honoring the magician who continues learning and working with passion.
  4. Now we should change the location of the about:blank (which belongs to our domain) to the one of nature. To do it, we will change the location using a meta-refresh to about:blank. Easy. That trick makes sure the about:blank recovers the domain of its parent.
  5. Let the opener know that everything’s ready so it can access, just like this: window.open(“javascript:alert(document.domain)”, “DAVID_COPPERFIELD”);

[ Click here to see the PoC ]

Update: [ Patched – CVE-2017-0002 ]

Enjoy again, but this time jumping and screaming around the house. Yes! Yes! Oopss, my wife is asking me what I found now. She knows what those screams mean. 😉

Mr killjoy, we made it again. The PoCs are interactive so we can learn in real time what we are doing, but please, go over them and read the specific details of the code. I’m sure there’s room for improvement, and if you make these ideas yours it’s highly likely that you will find variations to achieve similar things and more. Research, study, learn! It’s fun.

Can you find your own way to set a domainless URL without a Flash? Yes you can. Also, just with the code that we explored together here, we can create other UXSS scenarios like being inside an iframe that accesses its top. Is that possible? Let’s say we are a banner-ad inside an iframe rendered by Facebook. Is it possible to access our top, and get -for example- the list of a user’s friends? Of course! What about accessing XFO sites that don’t like to be framed? Is the iframe the only element capable of rendering HTML? Last, what about sites that do not have iframes at all? I give you my word, we can even access those ones twisting this code a bit. Sit down and explore! Here you have the needed files.

Have a nice day!
Manuel.