Both Microsoft Edge and Internet Explorer suffer from navigation problems, failing to keep up with the most updated history information. A framed navigation confuses these browsers and what seems to be a naive functionality problem ends up being a security bug: information disclosure across origins.

Let’s first examine the functionality problem by building a page using just an <iframe>. Take a look at the code below: the iframe src points to first_page.html but it navigates to second_page.html when clicking on the button.

<iframe src="first_page.html"></iframe> <input onclick="navigateIFrame()" value="Navigate IFrame" type="button"/> <script> function navigateIFrame() { window[0].location.href = "second_page.html"; } </script> 1 2 3 4 5 6 7 8 9 10 11 12 <iframe src = "first_page.html" > </iframe> <input onclick = "navigateIFrame()" value = "Navigate IFrame" type = "button" /> <script> function navigateIFrame ( ) { window [ 0 ] . location . href = "second_page.html" ; } </script>

The browser will render first_page.html in the iframe, and by clicking on the button its location will change to second_page.html. So let’s imagine that we have clicked and it points now to the second page.

What would be the source of the iframe if we move away from the main page and return back? Which content should be loaded in the iframe, the first or the second page? Pause for a second and think: the original source points to first_page, however, we changed its location and moved away from the main page. But we are back.

Navigated the iframe. Navigated the top window. Clicked on the back button of the browser.

The answer, as expected, is that all browsers will point to its most recent location in history: second_page.html. [ Try it Live ]

At this point everything works as expected, but we will dynamically insert an HTML element into the DOM of the main page and see it failing to properly navigate. We will use a <br /> but any tag is fine as long as we force the browser to render something before the iframe.

<iframe src="first_page.html"></iframe> <input onclick="navigateIFrame()" value="Navigate IFrame" type="button"/> <script> function navigateIFrame() { window[0].location.href = "second_page.html"; } // Inject HTML element in the DOM to "confuse" IE/Edge. document.body.insertAdjacentHTML ('afterBegin', '<br />'); </script> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <iframe src = "first_page.html" > </iframe> <input onclick = "navigateIFrame()" value = "Navigate IFrame" type = "button" /> <script> function navigateIFrame ( ) { window [ 0 ] . location . href = "second_page.html" ; } // Inject HTML element in the DOM to "confuse" IE/Edge. document . body . insertAdjacentHTML ( 'afterBegin' , '<br />' ) ; </script>

The result is different now: Edge overrides the latest location with the one in the src attribute of the iframe. In other words, the browser will load the original location of the iframe instead of the latest navigated one. [ Try it Live ]

Edge is replacing the last navigated location with the SRC attribute of the original iframe. To validate this, we can navigate the iframe several times and click on back to see where it goes. In the video below we can see a demo navigating 4 pages (1, 2, 3, 4) in the iframe and then navigating the top. Then we click on the back button only to see that the browser replaced the latest navigated page with the original location of the iframe.

The navigation history of the four pages looks like this: [1, 2, 3, 4] but after clicking the back button, the last location is replaced, like this [1, 3, 2, 1].

You might be asking at this point what is the problem with this functionality bug, right? Here’s the issue, all modern browsers have a feature that make our lives easier: they “remember” what we’ve typed during our navigation in all the sites as long as we keep the tab opened. So, if we go to a website and type anything inside an input box, no matter how many times we go forward or backward, the browser will remember our typed data and paste it properly into each page. Keep in mind that the original page inputs were probably empty so when going back and forth, the browser has to place that information back in to give us the illusion that nothing changed.

If we add up this feature with the navigation problem described above, we can fool the browser and make it “remember” (paste) the typed information in the wrong place. It’s actually pretty simple: build a webpage with an input box of the similar type of the site that we want to grab the text. In other words, we should emulate what the original page had so the browser can paste it comfortably. But no worries, this is quite easy. Let’s go to bing.com and see what’s the type of the search box input tag. Using the DOM inspector we can do it in a second.

The input tag type is “search” so we will craft a page with an input like that. All the other information is ignored anyway so we don’t have to worry about ids, classes, etc. Let’s build our tricky page and make this process automatic. Below is the abbreviated code, but you can see the full POC working here.

// File: index.html <iframe src="input.html"></iframe> <script> window[0].location.href = "http://www.bing.com"; </script> // Once the user types something, we navigate the top-page and come back. 1 2 3 4 5 6 7 8 9 / / File : index . html <iframe src = "input.html" > </iframe> <script> window [ 0 ] . location . href = "http://www.bing.com" ; </script> / / Once the user types something , we navigate the top - page and come back .

// File: input.html The input box below will be automatically completed by Edge <input type="search" id="box" /> <script> alert("Your search in Bing was: " + box.value); </script> 1 2 3 4 5 6 7 8 / / File : input . html The input box below will be automatically completed by Edge <input type = "search" id = "box" /> <script> alert ( "Your search in Bing was: " + box . value ) ; </script>

Have a great day, fellow bug hunter! Remember that persistence and passion are key. If you want work on this issue, grab the files from here.

Questions? Message me on Twitter @magicmac2000

Update (2016-09-05): the question from Jun made me think a bit, and now we have a shorter PoC which does not require us to go back and forth but just a reload. Still, I haven’t tried this without iframes. It might be possible using a pushState/replaceState plus a server redirect but we need to play a bit more. If you are particularly interested on getting a non-framed PoC let me know and I’ll do my best to find a way to get it.