脚本安全

此页面提供了 Gecko 中脚本安全架构的概述。

与任何 Web 浏览器一样,Gecko 可以从不受信任且可能存在敌意的网页加载 JavaScript 并将其在用户的计算机上运行。Web 内容的安全模型基于同源策略,其中代码可以完全访问其来源的对象,但对来自不同来源的对象的访问权限受到严格限制。确定一个对象是否与另一个对象同源以及允许跨源访问哪些访问的规则现在在浏览器之间大多是标准化的。

不过,Gecko 还存在一个额外的问题:虽然其核心是用 C++ 编写的,但前端代码是用 JavaScript 编写的。此 JavaScript 代码(通常称为chrome 代码)以系统特权运行。如果代码遭到破坏,攻击者可以接管用户的计算机。旧版 SDK 扩展也以 chrome 特权运行。

将浏览器前端置于 JavaScript 中有其好处:用 JavaScript 开发比用 C++ 开发快得多,并且贡献者无需学习 C++。但是,JavaScript 是一种高度动态、可塑的语言,如果没有帮助,很难编写与不受信任的 Web 内容安全交互的系统特权代码。从 chrome 代码的角度来看,Gecko 中的脚本安全模型旨在提供帮助,使编写安全、系统特权的 JavaScript 成为一项现实的期望。

安全策略

Gecko 实施以下安全策略

  • 同源的对象能够自由地相互访问。例如,与从https://example.org/提供的文档关联的对象可以相互访问,它们还可以访问从https://example.org/foo提供的对象。

  • 跨源的对象根据同源策略获得对其彼此的访问权限受到严格限制。例如,从https://example.org/提供的代码尝试访问来自https://somewhere-else.org/的对象将受到访问权限限制。

  • 在特权作用域中的对象允许完全访问权限较低作用域中的对象,但默认情况下它们会看到这些对象的受限视图,旨在防止它们被不受信任的代码欺骗。此作用域的一个示例是 chrome 特权 JavaScript 访问 Web 内容。

  • 在权限较低的作用域中的对象无法访问权限较高作用域中的对象,除非权限较高作用域显式地克隆这些对象。此作用域的一个示例是 Web 内容访问 chrome 特权作用域中的对象。

区隔

区隔是 Gecko 脚本安全架构的基础。区隔是内存中的特定、独立区域。在 Gecko 中,每个全局对象都有一个单独的区隔。这意味着每个全局对象及其关联的对象都位于其自己的内存区域中。

../../_images/compartments.png

当然,普通内容窗口是全局对象,但 chrome 窗口、沙箱、工作线程、框架脚本中的ContentFrameMessageManager等等也是如此。

Gecko 保证在给定区隔中运行的 JavaScript 代码只能访问同一区隔中的对象。当来自区隔 A 的代码尝试访问区隔 B 中的对象时,Gecko 会为其提供一个跨区隔包装器。这是区隔 A 中对真实对象的代理,该对象位于区隔 B 中。

../../_images/cross-compartment-wrapper.png

在同一区隔内,所有对象共享一个全局对象,因此它们彼此同源。因此,不需要任何安全检查,也没有包装器,并且对于单个窗口中的对象相互交互的常见情况没有性能开销。

每当发生跨区隔访问时,包装器使我们能够实施适当的安全策略。因为我们选择的包装器特定于两个区隔之间的关系,因此它实施的安全策略可以是静态的:当调用方使用包装器时,无需检查谁在进行调用或调用去向。

跨区隔访问

同源

正如我们已经看到的,同源访问最常见的场景是属于同一窗口对象的交互。所有这些都在同一区隔内进行,无需安全检查或包装器。

当对象共享一个来源但不是全局对象时——例如来自相同协议、端口和域的两个网页——它们属于两个不同的区隔,并且调用方会获得目标对象的透明包装器

../../_images/same-origin-wrapper.png

透明包装器允许访问所有目标的属性:从功能上讲,就像目标在调用方的区隔中一样。

跨源

如果两个区隔是跨源的,则调用方会获得一个跨源包装器

../../_images/cross-origin-wrapper.png

这会拒绝访问所有对象的属性,除了 Window 和 Location 对象的一些属性,如同源策略中所定义的那样。

从特权代码到非特权代码

此类安全关系最明显的示例是系统特权 chrome 代码和不受信任的 Web 内容之间,但 Gecko 中还有其他示例。附加组件 SDK 在沙箱中运行内容脚本,这些沙箱使用扩展主体进行初始化,赋予它们相对于其操作的 Web 内容的更高权限,但相对于 chrome 的权限降低。

如果调用方的权限高于目标对象,则调用方会获得对象的X光包装器

../../_images/xray-wrapper.png

X光旨在防止不受信任的代码通过以意外方式重新定义对象来混淆受信任的代码。例如,使用 X光访问 DOM 对象的特权代码只能看到 DOM 对象的原始版本。任何扩展属性都不可见,如果任何原生 DOM 属性已被重新定义,则它们在 X光中不可见。

如果特权代码希望不受限制地访问不受信任的对象,则可以放弃 X光。

有关 X光的更多信息,请参见X光视觉

从非特权代码到特权代码

如果调用方的权限低于目标对象,则调用方会获得一个不透明包装器

../../_images/opaque-wrapper.png

不透明包装器拒绝访问目标对象的所有内容。

但是,特权目标可以使用exportFunction()cloneInto()函数将对象和函数复制到权限较低的作用域中,然后权限较低的作用域就可以使用它们。

安全检查

为了确定两个区隔之间的安全关系,Gecko 使用两个概念:安全主体包含行为。为了建立区隔 A 和 B 之间的安全关系,Gecko 会询问

区隔 A 的安全主体是否包含区隔 B 的安全主体,反之亦然?

包含

A 包含 B

A 具有 B 的所有权限,甚至可能更多,因此 A 允许查看和执行 B 可以查看和执行的任何操作。

A 包含 B && B 包含 A

A 和 B 同源。

A 包含 B && B 不包含 A

A 的权限高于 B。

A 默认情况下可以访问 B 的所有内容,并使用 X光视觉,它可以选择放弃 X光视觉。

B 无法访问 A,尽管 A 可以选择将对象导出到 B。

A 不包含 B && B 不包含 A

A 和 B 跨源。

安全主体

安全主体有四种类型:系统主体、内容主体、扩展主体和空主体。

系统主体

系统主体通过所有安全检查。它包含自身和所有其他主体。根据定义,Chrome 代码以及框架脚本都以系统主体运行。

内容主体

内容主体与某些 Web 内容相关联,并由内容的来源定义。例如,普通 DOM 窗口具有由窗口来源定义的内容主体。内容主体仅包含具有相同来源的其他内容主体。它被系统主体、包含其来源的任何扩展主体以及任何其他具有相同来源的内容主体包含。

扩展主体

扩展主体指定为来源数组

["http://mozilla.org", "http://moz.org"]

扩展主体包含它包含的每个内容主体。即使扩展主体仅包含单个内容主体,内容主体也不会包含扩展主体。

因此["http://moz.org"]包含"http://moz.org",反之则不然。扩展主体可以完全访问它包含的内容主体,默认情况下使用 X光视觉,而内容主体无法访问扩展主体。

这还使脚本安全模型能够将具有扩展主体的区隔视为更像浏览器的一部分,而不是 Web 内容。这意味着,例如,当 Web 内容的 JavaScript 被禁用时,它可以运行。

当您想要赋予代码额外的权限(包括跨源访问)但又不想赋予代码完全的系统权限时,扩展主体非常有用。例如,附加组件 SDK 中使用扩展主体来为预定义的一组域提供内容脚本跨域权限,并保护内容脚本免受不受信任的 Web 内容访问,而无需赋予内容脚本系统权限。

空主体

空主体几乎无法通过所有安全检查。它没有任何权限,除了自身和 chrome 之外,任何东西都无法访问它。它不包含任何其他主体,即使是其他空主体。(当 HTML5 和其他规范说明“来源是全局唯一标识符”时,就会使用它。)

主体关系

下图总结了不同主体之间的关系。连接主体 A 和 B 的箭头表示“A 包含 B”。(A 是箭头的起点,B 是终点。)

../../_images/principal-relationships.png

计算包装器

下图显示了确定区隔 A 在尝试访问区隔 B 中的对象时获得的包装器类型的因素。

../../_images/computing-a-wrapper.png