Watch the 2 minutes exploit video where we manually tweet as if we were Charles Darwin, and get his password (thanks to the default password manager of Microsoft Edge). If you are out of time, watch the 1 minute video where we do the same thing, automatically.

Charles Darwin is an example, this Microsoft Edge vulnerability allows the attacker to tweet or do anything (password anyone?) from many websites on behalf of the logged user.

We are going to explore yet another SOP bypass on Microsoft Edge, but in this case abusing of the data/meta tags plus the fact that domainless pages have free access to domained ones.

[ Update: this bug was patched on 2017-06-13 ]

A domainless world, again

If this is your first time here, I suggest you reading these two SOP bypasses first: Adventures in a Domainless World (Edge) and More Adventures in a Domainless World (IE). The following post is based on the same idea, just a new technique.

Let’s quickly refresh on this important concept: about:blank pages have always the domain of their referrer, meaning that an about:blank from a twitter’s iframe can’t access to a google’s blank. Even if the address matches between them (about:blank), the document.domain is different.

In the past, we were able to create about:blanks without domains, or domainless about:blanks. Those ones have the power to access every about:blank regardless of its domain. For example, let’s say we have a domainless blank on the main page rendering an iframe pointing to twitter and another one to google. Those iframes have blank sub-iframes inside, and their domains are like their parents: twitter and google. In a case like this, the top window has access to the sub-iframes, completely bypassing the SOP.

The above scenario above worked perfectly well, but Microsoft patched it three months ago using a very clever trick: domainless blanks are not really domainless anymore, they are always set with a random GUID as their domain, like this: {53394a4f-8c04-46ab-94af-3ab86ffcfd4c}. And there’s something even more interesting (hat tip to Edge Developers): the domain will look as if it were blank/empty, but it is not. In other words, Edge will hide the GUID and return empty, but internally, it is still a GUID.

Let’s experiment! Open Edge with DevTools enabled (F12) and type about:blank into the address bar. This used to create a domainless blank and it still looks as if nothing has changed, but Edge is performing a trick for us. Appreciate its magic for a second please, we will have plenty of time to break the spell anyway.

As we can see, DevTools thinks we are on an empty domain, but it is not.

Breaking the spell

If even DevTools is being fooled, how can we see what’s really happening here? Easier than expected, just try to load anything with a relative path, or change the location of this window, or even a document.write will reveal the trick. Let’s use a location.href = 1 and see what happens.

Top data-uri patched and Flash disabled

Our previous technique to create domainless blanks has been patched. We were setting a data-uri on the main (top) window with the help of a Flash/GetURL. But the trick was patched and even worse, we can’t automatically run Flash anymore! In Windows Creators Update, Edge asks for permission to run Flash.

The PoCs from previous posts look ugly now! Kudos to the Edge team for reducing the attack surface here.

Finding a new domainless blank

Our data-uri trick against the top does not work anymore, so how can we overcome this? First of all, I started playing against an iframe instead of the top, because as we’ve seen in the past, Edge does not like data-uris in the main window.

top.location.href = "data:text/html,SOMETHING"; // Fails badly, error page 1 2 3 top . location . href = "data:text/html,SOMETHING" ; // Fails badly, error page

But setting a data-uri as the location of an iframe works well. However, this is not a bug, the iframe domain is isolated from the top.

As we’ve seen in the reader mode SOP bypass, the data-uri “isolated” restriction is trivial to bypass (just a self document.write and we can access the parent), but we don’t want that now. Accessing the top makes no sense right now, what we want is a way to get a domainless blank. For that, we will need a triple-combo: data-meta-data. That will make Edge gently give us back what it took from us!

To be concrete, we will set the location of an iframe with a data uri that renders a meta refresh which redirects to another data uri. That’s the moment where you, I, and even Edge get lost 🙂

Recipe to create a domainless blank: Set the location of an iframe to a data-uri The data-uri renders a meta refresh tag The meta refresh redirects to another data-uri

Let’s build a URL that converts a regular (domained) iframe into a domainless one. It is easier for me if I repeat this in my mind “data-meta-data”, because in reality, that’s all.

I know it is not as beautiful as E=mc2, but our trick could have been used to steal Einstein’s credentials, email, paypal account and even tweet something on his name. Let’s first test if what we’ve learned until this point really works. We will play with bing.com which is easy because it has an inner blank iframe and it does not use XFO.

Warming up with bing.com

We will create a webpage with two iframes: one with bing.com and one domainless. From the domainless we will execute code inside bing’s inner blank iframe. Bing images is perfect for our needs. Let me open Chrome for a second and show you what I mean.

This is great. Now we will frame this page and inject code into the blank iframe with the help of our domainless data-meta-data. But there’s something that I didn’t say, bug hunter. Do you remember the naming problem that we had with nature.com in our original domainless SOP? If not, let me give you a very quick refresh.

At this point, our domainless iframe is able to access bing’s blank, but, the access mechanism is extremely important. We can’t just access the DOM straight, we must use the window.open method. In other words, if bing is in the first iframe of the main page, we won’t be able to access its inner iframe this way:

alert(top[0][0].document.cookie); // ACCESS DENIED 1 2 3 alert ( top [ 0 ] [ 0 ] . document . cookie ) ; // ACCESS DENIED

In fact, we can’t even do this

top[0][0].location.href = "javascript:alert(document.cookie)"; // ACCESS DENIED 1 2 3 top [ 0 ] [ 0 ] . location . href = "javascript:alert(document.cookie)" ; // ACCESS DENIED

So, how can we do it? Very easy, a window open with a javascript url and the name of the iframe. If bing’s inner iframe name were “INNER_IFRAME“, the following code would work perfectly well:

window.open("javascript:alert(document.cookie)", "INNER_IFRAME"); // SOP BYPASSED! 1 2 3 window . open ( "javascript:alert(document.cookie)" , "INNER_IFRAME" ) ; // SOP BYPASSED!

But damn it! This nested iframe in Bing doesn’t have a name! Bug hunter, don’t panic. We can either request the Bing team to set a name for us, 🙂 or, we can keep pushing. Better to keep pushing!

Setting an iframe name

We can’t set the name of an iframe that we don’t own, unless it is in the same domain as we are. We are going to render a bing iframe that has a blank one inside. The outer iframe is on the different domain, but the tag itself (the element, the object) is in our domain, so we can set any name we want.

<iframe name="ANY_NAME" src="http://bing.com"> <iframe src="about:blank"></iframe> </iframe> 1 2 3 4 5 <iframe name = "ANY_NAME" src = "http://bing.com" > <iframe src = "about:blank" > </iframe> </iframe>

But the inner iframe is rendered by bing, and even if it’s blank, its domain is still bing.com. The only way to change its name is to first set its location to one that we can access, and only then change its name. Now, if we change the location of the about:blank it will be like shooting in the foot, because we need to be be bing.com so we can later access with the domainless blank.

Remember: our goal is to access from a domainless blank to a domained one. If we set the domain to match ours, then it makes no sense to access it! So here’s what we will do: we will set the location, change the iframe name and then, restore the location back in such a way that it will keep the original domain. Sounds complicated?

Recipe to set the name of an x-domain-iframe: Set the location of the iframe to about:blank, which changes its domain to the one that we control: cracking.com.ar Change the iframe name at will. Set its location again to about:blank, but this time using a meta refresh which makes its domain == its creator: bing.com in this case.

That’s it. Now the inner-iframe has a name and its domain is restored to bing.com! This is code:

// Sets the location of Bing's inner iframe to about:blank // But now it is in our domain so we can set a name to it. window[0][0].location = "about:blank"; // Set the inner iframe name to "CHARLES" so we can later inject code // using a window.open("javascript:[...]","CHARLES"); window[0][0].name = "CHARLES"; // Restore Bing's domain to the about:blank that we've just renamed. window[0][0].document.write('<meta http-equiv="refresh" content="0;url=about:blank">'); window[0][0].document.close(); 1 2 3 4 5 6 7 8 9 10 11 12 13 // Sets the location of Bing's inner iframe to about:blank // But now it is in our domain so we can set a name to it. window [ 0 ] [ 0 ] . location = "about:blank" ; // Set the inner iframe name to "CHARLES" so we can later inject code // using a window.open("javascript:[...]","CHARLES"); window [ 0 ] [ 0 ] . name = "CHARLES" ; // Restore Bing's domain to the about:blank that we've just renamed. window [ 0 ] [ 0 ] . document . write ( '<meta http-equiv="refresh" content="0;url=about:blank">' ) ; window [ 0 ] [ 0 ] . document . close ( ) ;

Let me give you the good news: we don’t really need sites with “about:blank” iframes because we can always do as we did above. In other words, it does not matter if bing’s inner iframe was about:blank on not because we ended up setting it as blank with its original domain! I don’t know how you feel bug hunter, but I feel exactly like this.

The doors are open for us! We can finally run the window.open from our data-meta-data iframe:

window.open("javascript:alert(document.cookie)", "CHARLES"); // Fireeeeeeeeeee!!! 1 2 3 window . open ( "javascript:alert(document.cookie)" , "CHARLES" ) ; // Fireeeeeeeeeee!!!

One important thing regarding the PoC, bug hunter: in the sample above we are using an http (insecure) connection because in https the meta refresh to data is blocked so it never redirects to the final data uri. Edge incorrectly thinks that the redirection is insecure. However, this can be easily bypassed by using a document.write. So instead of a data-meta-data it should be document.write(meta-data). Makes sense?

We are not using it in the PoC above, because when making the demo interactive (making you press buttons to run it) Edge crashes (non-exploitable ) a third of the times. So, I’ve chosen to use a guided and reliable PoC in http vs https automatic. But either way, as we will see below it does not really matter: our insecure domainless page is capable of accessing secure pages. Let’s build a real case.

A real attack: Stealing Charles cookies

It is the time to build an attack, bug hunter. Join me into the time machine where we can fly to the past, bringing computers and Internet to the geniuses that preceded us. Charles Darwin is thinking about how species change over time, while Alfred Wallace is having similar ideas. Charles is aware of hackers so he uses his computer in paranoid mode: he never opens a link in using the same browser window where he has his gmail, twitter, and personal documents.

Take a look, bug hunter. He is opening a new InPrivate window from the task bar!

He is in good mood, to the point that he opens another tab with his Twitter account, and teases Alfred Wallace about who will tweet first.

The response from Wallace came a few hours later, and it included a link with evidence supporting his claims. But remember, Charles did not trust anyone so he copied the link in order to paste it into a new window, away from the one where he had his personal data (gmail, twitter).

What can go wrong? Everything! Twitter has several iframes, just like most sites in the world. In fact, it has two blank iframes with names, so this should be easier than Bing! But before going back to our story 🙂 let’s enumerate twitter’s iframes using DevTools to find a good candidate. I’m opening a different window here, nothing to do with Charles session.

Excellent! dm-post-iframe looks good, so we have all we need to take over Charlie’s account.

Charles opened a new inPrivate window and loaded the URL that Wallace sent him. What he didn’t know was that browser windows, even inPrivate, can communicate with each other. So, what will happen if we execute the code below in our domainless iframe?

window.open("javascript:alert(document.cookie)", "dm-post-iframe"); 1 2 3 window . open ( "javascript:alert(document.cookie)" , "dm-post-iframe" ) ;

You are totally right. We have Charles Darwin cookies.

WARNING: the following proof of concept will really alert (just show in your screen) your Twitter cookies.

Keep in mind that we don’t really need the InPrivate windows. The example above tries to illustrate a paranoid scenario, but normally things are even easier because people click on links without doing all the work that Charles did to prevent the attack. Also, consider that attackers use malvertising, deploying their bad bits inside cheap banners from popular sites. If an attacker is hosted inside a Yahoo banner and the user is logged in into her Twitter account, she will be pwned with no interactions, at all.

Tweeting like Charles Darwin

Let’s build a better PoC. Instead of reading his cookies, we will tweet on his name and even try to grab his password. Keep in mind that most users (like Charlie) use password managers that auto-fill their passwords. Edge password manager is no different, so, if Charles has saved his password, we will get it. It’s not hard, just force him a log out and the log-in page will be automatically loaded, with all his data (username and password) server to us in a silver player. In fact, in this case the form is hidden unless the user interacts, but Edge is filling it anyway so we don’t even have to make the form visible.

Before running the PoC, consider that it is your account the one that will be exposed, not Charles. Nothing is being sent to the network but if there’s somebody behind you, she will see your password in a regular alert dialog. Watch out!

Quick questions that came to my mind:

Can we use this in sites without about:blank iframes? Of course! We can even do it in sites that don’t have iframes at all. Please, read this post where we inject an iframe on a different origin. Same here on Internet Explorer.

Is it possible to do the same thing, in Facebook? I don’t have a facebook account, so I did not test. But a SOP bypass is capable of accessing every single domain in the planet. It could be a little bit harder to exploit, but impossible? Remember the mantra of the offensive-security guys: try harder.

Does this work in other browsers? I did not try, but obviously not. UXSS/SOP bypasses tend to be particular to each browser.

The exploit code is easy. Please get it and take a look, but if you have questions, ask! I stop here, bug hunter, otherwise this blog will be renamed to brokenmarriage.com 🙂 It’s late night in Buenos Aires, time to be with my wife!

Have a nice day!

Manuel.