在沙盒化 iframe 中安全畅玩

Mike West

在当今的网络世界中营造丰富的体验几乎不可避免地涉及到 嵌入组件和内容,而您无法真正控制这些组件和内容。 第三方微件可以提高互动度, 而用户生成的内容有时更为重要 与网站的原生内容相比不明智的做法是放弃其中之一 都会增加网站发生“Something BadTM”问题的风险。每个 您嵌入的每个广告、每个社交媒体微件, 攻击途径:

内容安全政策 (CSP) 通过提供 让您可以将特别受信任的脚本来源和其他来源 内容。这是朝着正确方向迈出的重要一步,但需要注意的是, 大多数 CSP 指令提供的保护是二进制的:资源是 或者不允许。有时候,“我不是 我确实信任这些内容来源,但它实在是太美了!嵌入 但请不要让它破坏我的网站。”

最小权限

从本质上讲,我们正在寻找一种机制, 只嵌入工作所需的最低级别功能。如果微件 无需弹出新窗口,取消对 window.open 的访问权限 伤害。如果广告素材不需要 Flash,则关闭插件支持 问题。如果遵循最低限度原则,我们就会尽可能地确保安全 权限和屏蔽设置 与我们希望的功能不直接相关的每个功能 使用。如此一来,我们就不再需要盲目相信某些部分, 不会利用本不应使用的权限。它 他们最初根本无法使用这项功能

若要构建此类解决方案的良好框架,首先要做的就是使用 iframe 元素。 在 iframe 中加载一些不受信任的组件可以衡量隔离度 您的应用与您要加载的内容之间。加框链接的内容 将无法访问页面的 DOM,也无权访问您本地存储的数据 能够在网页上的任意位置画像;它的使用范围仅限于 轮廓线不过,这种分离并不真正可靠。包含的页面 仍然有许多令人厌烦或恶意行为的选项:自动播放 视频、插件和弹出式窗口只是冰山一角。

iframe 元素的 sandbox 属性 提供了加强对加框内容的限制所需的工具。我们可以 指示浏览器以较低权限加载特定框架的内容 从而仅允许执行任何操作所需的功能子集 需要完成哪些工作

Twust,但是验证

Twitter 的“Tweet”按钮就是一个很好的功能示例, 安全地嵌入到您的网站上Twitter 允许您将 通过 iframe 加载按钮 替换为以下代码:

<iframe src="https://platform.twitter.com/widgets/tweet_button.html"
        style="border: 0; width:130px; height:20px;"></iframe>

为了弄清楚我们可以锁定什么,让我们仔细研究一下 按钮要求。加载到框架中的 HTML 会执行一些 并生成填充 推送至 Twitter 微博界面。该界面需要访问 Twitter 的 Cookie 以将 Twitter 微博与正确的账号关联起来,并且需要能够 提交推文表单差不多就是这样。帧不需要 无需打开顶级窗口或加载任何插件 很多其他功能。由于它不需要这些权限 就可以通过将框架的内容沙盒化来移除它们

沙盒基于白名单运行。首先,我们先删除所有 然后重新启用各项功能,只需添加 指定标记来指定沙盒的配置。对于 Twitter 微件,我们 决定启用 JavaScript、弹出式窗口、表单提交和 twitter.com 的 Cookie。为此,我们可以在 iframe 中添加 sandbox 属性,并使用 以下值:

<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
    src="https://platform.twitter.com/widgets/tweet_button.html"
    style="border: 0; width:130px; height:20px;"></iframe>

大功告成。我们已经赋予框架所需的全部功能 浏览器会拒绝它访问我们未授权的 通过 sandbox 属性的值明确授予该权限。

对功能进行精细控制

在上面的示例中,我们看到了一些可能的沙盒标记, 更加详细地探讨属性的内部运作方式。

如果 iframe 的沙盒属性为空,则加框文档将完全经过沙盒化处理,使其遵循 并受到以下限制:

  • JavaScript 不会在框架文档中执行。这不仅包括 JavaScript 通过脚本代码明确加载,还通过内嵌事件处理脚本加载 和 javascript: 网址。这也意味着,noscript 标记中包含的内容 将会显示,这与用户自己停用了脚本一样。
  • 加框文档会加载到一个唯一的源,这意味着: 同源检查将失败;唯一的源与其他任何源都不匹配, 甚至包括自身。这意味着文档内容 访问存储在任何源的 Cookie 或任何其他存储机制中的数据 (DOM 存储、索引型数据库等)。
  • 框架文档无法新建窗口或对话框(通过 window.opentarget="_blank")。
  • 无法提交表单。
  • 插件无法加载。
  • 带框架的文档只能自行导航,无法为其顶级父级导航。 设置 window.top.location 会抛出异常,点击包含以下内容的链接 target="_top" 不会产生任何影响。
  • 自动触发的功能(自动聚焦的表单元素、自动播放) 视频等内容)都会遭到屏蔽。
  • 无法获取指针锁。
  • 在框架文档包含的 iframes 上,系统会忽略 seamless 属性。

这非常严肃,将一个文档加载到完全沙盒化的 iframe 中。 风险也很小当然,它也没有太多价值: 也许能够针对某些静态内容使用完整的沙盒 多数时候你就要放宽一些要求

除插件外,所有这些限制都可以通过 向沙盒属性的值添加标记。沙盒化文档永远不会 运行插件,因为插件是未经过沙盒化的原生代码,但其他一切都是正常的 游戏:

  • allow-forms 允许提交表单。
  • allow-popups 允许显示弹出式窗口。
  • allow-pointer-lock 允许(令人惊讶!)指针锁定。
  • allow-same-origin 允许文档保留其来源;已加载页面 来自https://example.com/的个人资料将保留对该来源的数据的访问权限。
  • allow-scripts 允许执行 JavaScript,还允许一些功能 (因为通过 JavaScript 实现起来很简单)。
  • allow-top-navigation 可让文档按照 打开顶级窗口

了解了这一点,我们就可以准确评估最终为什么 上述 Twitter 示例中的一组沙盒标记:

  • allow-scripts 是必需的,因为加载到框架中的页面会运行一些 JavaScript 来处理用户互动。
  • allow-popups 是必需的,因为按钮会在新的 窗口。
  • allow-forms 为必填项,因为推文表单应可提交。
  • allow-same-origin 是必要的,否则 twitter.com 的 Cookie 无法访问,且用户无法登录以发布表单。

需要注意的一点是 适用于在沙盒中创建的任何窗口或框架。这意味着 将 allow-forms 添加到帧的沙盒中,即使该表单仅存在 弹出窗口的窗口

有了 sandbox 属性后,widget 将仅获得其 并且仍会保留插件、顶部导航和指针锁定等功能 已被屏蔽。我们降低了嵌入 widget 的风险,而且没有造成任何不良影响。 对所有人来说,这都是一个赢家。

权限分离

对第三方内容进行沙盒化,以便在 显然,低特权环境非常有用但您的 ?你相信自己,对吧?那么为什么要担心沙盒呢?

我来回答这个问题:如果您的代码不需要插件, 对插件的访问权限?最好的情况是,你永远都用不到这种特权,最坏的则是你 这样可能会使攻击者有机会一探究竟。每个人的代码都有 而且几乎所有应用都容易受到在某一方面被利用的漏洞 或其他。将您自己的代码沙盒化意味着即使攻击者成功地 破坏您的应用,他们不会获得完整 应用的来源;他们将只能执行应用能够执行的操作 用途。这也不错,但还不如预期。

您可以将应用拆分为 逻辑部分,并以尽可能小的权限对每个部分进行沙盒化。 这种方法在原生代码中很常见:例如,Chrome 会自行破坏自身 访问本地硬盘的高权限浏览器进程, 并能建立网络连接和许多低权限渲染程序进程, 轻松解析不受信任的内容。渲染程序无需触摸屏幕 浏览器会负责为他们提供 呈现网页。即使聪明的黑客找到了破坏渲染程序的方法, 因为渲染器本身无法执行很多操作: 所有高权限访问都必须通过浏览器的进程进行路由。 攻击者需要在系统的不同部分找出几个漏洞 从而极大地降低成功赢利的风险。

eval() 进行安全沙盒化

通过沙盒和 postMessage API、 该模型能否成功运用到网络相当简单。碎片 您的应用可以位于沙盒化 iframe 中,并且父文档可以 通过发布消息和监听 响应。这种结构可以确保 尽量减少损害。它还有一个优势就是 创建清晰的集成点,以便您准确了解需要 请谨慎验证输入和输出。我们来看一个玩具示例 只是想看看如何做到这一点

Evalbox 是一款令人兴奋的应用 该方法接受一个字符串并将其评估为 JavaScript。哇,对吧?内容 你终于等了这么久。这是一个相当危险 因为允许执行任意 JavaScript 意味着 来源提供的所有数据都可供抓取我们将降低 Bad ThingsTM 通过确保代码在沙盒内执行 这使得它更加安全我们将从 先从框架的内容开始:

<!-- frame.html -->
<!DOCTYPE html>
<html>
    <head>
    <title>Evalbox's Frame</title>
    <script>
        window.addEventListener('message', function (e) {
        var mainWindow = e.source;
        var result = '';
        try {
            result = eval(e.data);
        } catch (e) {
            result = 'eval() threw an exception.';
        }
        mainWindow.postMessage(result, event.origin);
        });
    </script>
    </head>
</html>

在框架内,我们有一个非常小的文档,仅用于监听消息 (通过挂接到 window 对象的 message 事件),从其父项中移除。 每当父级在 iframe 的内容上执行 postMessage 时,此事件都会触发 触发后,我们就能够获取父级希望我们执行的字符串 。

在处理程序中,我们获取事件的 source 属性,该属性是父级 窗口。完成验证后,我们会使用此信息将 完成。然后,我们来完成繁重的工作:将获得的数据传递到 eval()。由于禁止的操作,此调用已封装在 try 块中 在沙盒化的 iframe 中经常会生成 DOM 异常;我们将捕捉 并报告友好的错误消息。最后,我们将结果 返回父窗口。整个过程非常简单。

父级同样简单。我们将使用 textarea 创建一个小型界面, 用于代码,使用 button 执行代码。我们将通过frame.html 在沙盒中运行的 iframe,仅允许执行脚本:

<textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
        id='sandboxed'
        src='frame.html'></iframe>

现在,我们来连接代码以便执行。首先,我们监听来自 iframe,并alert()提供给我们的用户。大概是真实的应用 则会执行不太令人生厌的操作:

window.addEventListener('message',
    function (e) {
        // Sandboxed iframes which lack the 'allow-same-origin'
        // header have "null" rather than a valid origin. This means you still
        // have to be careful about accepting data via the messaging API you
        // create. Check that source, and validate those inputs!
        var frame = document.getElementById('sandboxed');
        if (e.origin === "null" &amp;&amp; e.source === frame.contentWindow)
        alert('Result: ' + e.data);
    });

接下来,我们将连接一个事件处理脚本来点击 button。当用户 我们将获取 textarea 的当前内容,并将其传递到 执行框架:

function evaluate() {
    var frame = document.getElementById('sandboxed');
    var code = document.getElementById('code').value;
    // Note that we're sending the message to "*", rather than some specific
    // origin. Sandboxed iframes which lack the 'allow-same-origin' header
    // don't have an origin which you can target: you'll have to send to any
    // origin, which might alow some esoteric attacks. Validate your output!
    frame.contentWindow.postMessage(code, '*');
}

document.getElementById('safe').addEventListener('click', evaluate);

很简单,对吧?我们创建了一个非常简单的评估 API,可以确信 接受评估的代码无权访问 Cookie 等敏感信息 或 DOM 存储同样,经过评估的代码也无法加载插件、弹出新窗口或 或任何其他令人厌烦的或恶意的活动。

您可以将单体式应用分解成 单一用途组件每种方法都可以封装在一个简单的消息传递 API 中, 就像上面写的那样高权限父窗口可充当 控制器和调度程序将消息发送到 完成工作、监听结果以及 确保每个模块仅提供所需信息。

但请注意,处理加框链接的内容时需要非常小心 与父级同源。如果某网页位于 https://example.com/ 使用沙盒为同一来源的另一个网页构建框架 (包含 allow-same-originallow-scripts 标志),则 通过加框页面与父页面接触,可移除沙盒属性

在沙盒中畅玩

现在,您可以在多种浏览器中使用沙盒功能:Firefox 17+、 IE10+ 和 Chrome 浏览器(当然, 支持表格)。应用 sandbox 属性设为 iframes,您可以通过添加来向 所显示的内容,而授予 才能正常运行。这样,您便有机会降低 提供的内容, 已经实现内容安全 政策

而且,沙盒也是一种强大的技术,可以降低 攻击者将能够利用您代码中的漏洞。通过将 集成到一组沙盒化服务中,每项服务负责一个 自包含功能的小部分,攻击者将不得不 只会影响同时还要负责其控制器那是 这要困难得多,尤其是当控制器可以大幅降低时, 范围内。如果您满足以下条件,则可以花掉安全相关工作来审核这些代码 你可以让浏览器帮助你完成其余设置

这并不是说沙盒就完全可以解决 确保互联网安全它提供了纵深防御,除非您 您用户的但您还不能依赖浏览器支持来实现所有 (如果您确实控制着用户的客户端 - 企业环境 例如,太棒了!)。总有一天...但现在,沙盒是 以强化您的防御,但这并不是 值得信赖不过,图层的效果还是很棒。建议您利用 一个。

延伸阅读

  • HTML5 应用中的特权分离” 一篇有趣的论文涉及一个小型框架的设计, 及其应用到三个现有的 HTML5 应用

  • 与另外两种新 iframe 结合使用时,沙盒可更加灵活 属性:srcdoc、 和 seamless。 借助前者,你可以在框架中填充内容 HTTP 请求,后者可将样式融入框架内容中。 目前,两者的浏览器支持都相当糟糕(Chrome 和 WebKit) 每晚)。但将来会是一个很有趣的组合。您可以 例如,通过以下代码对文章进行沙盒评论:

        <iframe sandbox seamless
                srcdoc="<p>This is a user's comment!
                           It can't execute script!
                           Hooray for safety!</p>"></iframe>