在 Chrome 扩展程序中使用 eval

Chrome 的扩展程序系统会强制执行相当严格的默认内容安全政策 (CSP)。 政策限制非常简单:脚本必须移出线程,放入单独的 JavaScript 文件中,内嵌事件处理脚本必须转换为使用 addEventListener,并且 eval() 处于停用状态。Chrome 应用采用更严格的政策,我们对这些政策提供的安全特性非常满意。

不过,我们也了解到,许多库都使用 eval()eval 类结构(例如 new Function())来优化性能和简化表达。模板库尤其容易出现这种实现方式。虽然某些环境(如 Angular.js)支持 CSP out 许多热门框架尚未更新为与 Android 兼容 扩展程序的少了 eval 的世界。因此,事实证明,取消对该功能的支持 或性能超出预期的问题

本文档介绍了沙盒作为一种安全机制,可让您在项目中添加这些库,而不会影响安全性。为简洁起见,我们将全程使用附加信息一词,但 这一概念同样适用于应用。

为什么要使用沙盒?

eval 在扩展程序中很危险,因为它执行的代码可以访问扩展程序的高权限环境中的所有内容。有许多强大的 chrome.* API 可用,它们可能会严重影响用户的安全和隐私;简单的数据渗漏根本不是我们最担心的问题。我们提供的解决方案是一个沙盒,eval 可以在其中执行代码,而无需访问扩展程序的数据或扩展程序的高价值 API。没有数据、不使用 API,也没有关系。

为此,我们会将扩展程序软件包内的特定 HTML 文件列为已沙盒化。 每当加载沙盒化页面时,该页面都会移至唯一源,并且将被拒绝访问 chrome.* API。如果通过 iframe 将此沙盒化页面加载到扩展程序中, 然后向其传递消息,让其以某种方式处理这些消息,然后等待它为我们传回 结果。这种简单的消息传递机制为我们提供了安全纳入由 eval 驱动的一切所需的一切 代码生成。

创建和使用沙盒。

如果您希望直接查看代码,请下载沙盒示例扩展程序 关闭。这是一个基于 Handlebars 构建的小型消息传递 API 的有效示例 模板库,它应该会为您提供开始操作所需的一切。对于希望了解更多说明的用户,我们来一起详细了解一下该示例。

列出清单中的文件

必须通过添加 sandbox 属性,在扩展程序清单中列出应在沙盒中运行的每个文件。这是非常重要的一步,而且很容易忘记,因此请仔细检查您的沙盒化文件是否已列在清单中。在此示例中,我们将对名为“sandbox.html”的文件进行沙盒化处理。清单条目如下所示:

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

加载沙盒化文件

为了对沙盒化文件执行一些有趣的操作,我们需要将其加载到扩展程序代码可以访问的上下文中。在这里,sandbox.html 已通过 iframe 加载到扩展程序的事件页面 (eventpage.html)。eventpage.js 包含以下代码:每当用户点击浏览器操作时,该代码都会通过在页面上查找 iframe 并对其 contentWindow 执行 postMessage 方法,将消息发送到沙盒。消息是一个包含两个属性(contextcommand)的对象。我们稍后会深入探讨这两种方法。

chrome.browserAction.onClicked.addListener(function() {
 var iframe = document.getElementById('theFrame');
 var message = {
   command: 'render',
   context: {thing: 'world'}
 };
 iframe.contentWindow.postMessage(message, '*');
});
如需了解有关 postMessage API 的一般信息,请参阅关于 MDN 的 postMessage 文档 。非常完整,值得一读。尤其要注意,只有当数据可序列化时,才可以来回传递数据。例如,函数就不是。

做一些危险的事情

加载 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 插入它不会构成重大安全风险,因为即使通过某种巧妙的攻击完全破坏沙盒化代码,也无法将危险的脚本或插件内容注入高权限扩展程序上下文。

这种机制使模板化变得简单直接,但当然不限于模板化。在严格的内容安全政策下,任何无法直接运行的代码都可以放入沙盒中;事实上,将扩展程序中正确运行的组件放入沙盒中通常很有用,这样可以将程序的每个部分限制为仅拥有执行所需的最少特权集。Google 的编写安全的 Web 应用和 Chrome 扩展程序演示文稿 2012 年 Google I/O 大会给出了一些很好的实践示例,值得您花 56 分钟的时间 。