Использование eval в расширениях Chrome,Использование eval в расширениях Chrome

Система расширений Chrome применяет довольно строгую политику безопасности контента (CSP) по умолчанию. Ограничения политики просты: скрипт должен быть перемещен вне строки в отдельные файлы JavaScript, встроенные обработчики событий должны быть преобразованы для использования addEventListener , а eval() отключен. Приложения Chrome имеют еще более строгую политику , и мы вполне довольны свойствами безопасности, которые обеспечивают эти политики.

Однако мы понимаем, что различные библиотеки используют eval() и подобные eval конструкции, такие как new Function() для оптимизации производительности и простоты выражения. Библиотеки шаблонов особенно склонны к такому стилю реализации. Хотя некоторые (например, Angular.js ) поддерживают CSP «из коробки», многие популярные фреймворки еще не обновились до механизма, совместимого с миром расширений без eval . Поэтому удаление поддержки этой функциональности оказалось для разработчиков более проблематичным, чем ожидалось .

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

Почему песочница?

eval опасен внутри расширения, поскольку код, который он выполняет, имеет доступ ко всему, что находится в среде расширения с высокими разрешениями. Доступно множество мощных API-интерфейсов chrome.* , которые могут серьезно повлиять на безопасность и конфиденциальность пользователя; простая утечка данных — это наименьшее из наших беспокойств. Предлагаемое решение представляет собой «песочницу», в которой eval может выполнять код без доступа ни к данным расширения, ни к его важным API-интерфейсам. Нет данных, нет API, нет проблем.

Мы достигаем этого, указывая определенные HTML-файлы внутри пакета расширения как изолированные. Всякий раз при загрузке изолированной страницы она будет перемещена в уникальный источник и ей будет отказано в доступе к API chrome.* . Если мы загрузим эту изолированную страницу в наше расширение через iframe , мы сможем передавать ей сообщения, позволить ей каким-то образом воздействовать на эти сообщения и ждать, пока она вернет нам результат. Этот простой механизм обмена сообщениями дает нам все необходимое для безопасного включения кода, управляемого eval , в рабочий процесс нашего расширения.

Создание и использование песочницы.

Если вы хотите сразу погрузиться в код, возьмите образец расширения для песочницы и приступайте к работе . Это рабочий пример крошечного API обмена сообщениями, построенного на основе библиотеки шаблонов Handlebars , и он должен дать вам все необходимое для начала работы. Для тех из вас, кто хочет дополнительных объяснений, давайте вместе пройдемся по этому примеру здесь.

Список файлов в манифесте

Каждый файл, который должен запускаться внутри песочницы, должен быть указан в манифесте расширения путем добавления свойства sandbox . Это важный шаг, и о нем легко забыть, поэтому дважды проверьте, что ваш изолированный файл указан в манифесте. В этом примере мы помещаем в «песочницу» файл с хитрым названием «sandbox.html». Запись манифеста выглядит следующим образом:

{
  ...,
  "sandbox": {
     "pages": ["sandbox.html"]
  },
  ...
}

Загрузите файл в песочнице

Чтобы сделать что-то интересное с файлом в песочнице, нам нужно загрузить его в контексте, где к нему может обращаться код расширения. Здесь sandbox.html загружается на страницу событий расширения ( eventpage.html ) через iframe . eventpage.js содержит код, который отправляет сообщение в песочницу при каждом нажатии действия браузера путем поиска iframe на странице и выполнения метода postMessage для его contentWindow . Сообщение представляет собой объект, содержащий два свойства: context и command . Мы углубимся в оба через мгновение.

chrome.browserAction.onClicked.addListener(function() {
 var iframe = document.getElementById('theFrame');
 var message = {
   command: 'render',
   context: {thing: 'world'}
 };
 iframe.contentWindow.postMessage(message, '*');
});
Для получения общей информации об API postMessage ознакомьтесь с документацией postMessage на MDN . Это довольно полное издание, которое стоит прочитать. В частности, обратите внимание, что данные можно передавать туда и обратно, только если они сериализуемы. Функции, например, нет.

Сделайте что-нибудь опасное

Когда sandbox.html загружается, он загружает библиотеку Handlebars, создает и компилирует встроенный шаблон так, как предлагает Handlebars:

<script src="handlebars-1.0.0.beta.6.js"></script>
<script id="hello-world-template" type="text/x-handlebars-template">
  <div class="entry">
    <h1>Hello, !</h1>
  </div>
</script>
<script>
  var templates = [];
  var source = document.getElementById('hello-world-template').innerHTML;
  templates['hello'] = Handlebars.compile(source);
</script>

Это не провал! Несмотря на то, что Handlebars.compile в конечном итоге использует new Function , все работает точно так, как ожидалось, и в итоге мы получаем скомпилированный шаблон в templates['hello'] .

Передайте результат обратно

Мы сделаем этот шаблон доступным для использования, настроив прослушиватель сообщений, который будет принимать команды со страницы событий. Мы будем использовать переданную command , чтобы определить, что следует сделать (вы можете себе представить, что делаете нечто большее, чем просто рендеринг; возможно, создаете шаблоны? Возможно, каким-то образом управляете ими?), и context будет передан в шаблон непосредственно для рендеринга. . Отрисованный HTML-код будет передан обратно на страницу события, чтобы расширение могло сделать с ним что-нибудь полезное позже:

<script>
  window.addEventListener('message', function(event) {
    var command = event.data.command;
    var name = event.data.name || 'hello';
    switch(command) {
      case 'render':
        event.source.postMessage({
          name: name,
          html: templates[name](event.data.context)
        }, event.origin);
        break;

      // case 'somethingElse':
      //   ...
    }
  });
</script>

Вернувшись на страницу событий, мы получим это сообщение и сделаем что-нибудь интересное с переданными нам html данными. В этом случае мы просто отобразим это через уведомление на рабочем столе , но вполне возможно безопасно использовать этот HTML как часть пользовательского интерфейса расширения. Вставка его через innerHTML не представляет значительного риска для безопасности, поскольку даже полная компрометация изолированного кода с помощью какой-либо умной атаки не позволит внедрить опасный скрипт или содержимое плагина в контекст расширения с высокими разрешениями.

Этот механизм упрощает создание шаблонов, но, конечно, он не ограничивается шаблонами. Любой код, который не работает «из коробки» в соответствии со строгой политикой безопасности контента, может быть помещен в «песочницу»; на самом деле, часто бывает полезно изолировать компоненты ваших расширений, которые будут работать корректно, чтобы ограничить каждую часть вашей программы наименьшим набором привилегий, необходимых для ее правильного выполнения. Презентация «Написание безопасных веб-приложений и расширений Chrome» на конференции Google I/O 2012 дает несколько хороших примеров использования этих методов в действии и стоит 56 минут вашего времени.