【好文翻译】JavaScript 中的 realm 是什么?

本文由体验技术团队黄琦同学翻译。

原文链接: https://weizmangal.com/2022/10/28/what-is-a-realm-in-js/

github仓库地址: https://github.com/weizman/weizman.github.io/blob/gh-pages/_posts/2020-02-02-what-is-a-realm-in-js.md

前言

作为我对浏览器 JavaScript 安全性的长期研究的一部分,在过去的一年里,我一直特别关注 realms 的安全性⭐️。

由于“基于依赖库”的开发模式的兴起,JavaScript 生态系统(尤其是浏览器里面的 JavaScript 生态系统)也就更容易受到所谓的“供应链攻击”。JavaScript 中存在一种“创建新 realm”的能力,这一能力也被成功利用来对 web 应用程序实施供应链攻击(如果你想知道为什么它能被用于实施供应链攻击,建议阅读我以前发的这篇推文)。

在“raelm 安全”这一领域,我们目前还处于任重道远的状态。我希望通过引入一款开源工具来逐步解决这个问题。这是第一款开源的 realms 安全工具——Snow-JS ❄️,作者是 LavaMoat 🌋 。这个库还在早期阶段,正在演进中,敬请期待!

为了讲清楚我做这些事情的意义,我们必须先理解“什么是 realm”。显然,我们很难用一种正确、通俗、有启发性的方式去回答这个问题。

这篇文章主要围绕浏览器中的 JavaScript 进行讨论,它也有可能通用于所有的 JavaScript 环境,这我无法保证。

realm —— JavaScript 生存的世界

你可以通俗地认为,realm 大致就是一个生态系统,JavaScript 程序就生存在这个生态系统中。就如同其他任何一个生态系统一样,它里面包含了 JavaScript 程序生存所需的各种要素。

那么,哪些东西是 JavaScript 程序所需的呢?

1) 全局执行环境

在 JavaScript 中,可以有许多不同的脚本在同一环境中运行。这些脚本可以形成作用域(scopes)。作用域是一种符合规范的执行环境,值和表达式在其中“可见”或可被访问。作用域也可以堆叠成层次结构,子作用域可以访问父作用域,反过来则不行:

<script>(function scope1() {const x = 1;(function scope2() {const y = x + 2; // 3}());const z = x + y;  // Uncaught ReferenceError: y is not defined}());
</script>

在上面的示例中,我们展示了如何使用 JavaScript 定义作用域。但是,假如我们编写了一个 JavaScript 程序,它只声明变量,而没有声明作用域,那会怎么样呢?

这被称为“顶级声明(top level declaration)”——任何没在已定义的作用域内声明或运行的东西,它们都位于默认的最外层作用域(outer-most scope)下,即全局执行环境(global execution environment)。

在这个最外层作用域下面声明的变量,它们会被全局执行环境下的不同脚本所共享:

<script> const x = 1; </script>
<script> const y = 2; </script>
<script> const z = x + y; // 3 </script>

realm 为 JavaScript 程序提供了属于自己的一个全局执行环境

上述的示例使用的是 const,它的作用是在“声明式环境(declarative environment)”中定义了一个新东西,它与 letclassmoduleimport 以及 function 声明是一类东西。

用其他方式定义出来的东西都是在“对象式环境(object environment)”中,包括 var, function, async function, function*, async function* (* 表示生成器函数)。

注意,当 JavaScript 代码在严格模式下执行以及作为模块代码执行时,不同的声明语句通过“对象式环境(object environment)”对全局对象(global object)的影响与上述解释是有偏差的!

“声明式环境(declarative environment)”和“对象式环境(object environment)”共同构成了前面提到的全局执行环境。

除了上述内容外,“对象式环境(object environment)”还提供了所有的“内置全局对象(built-in global objects)”,因为它的基础对象就是所谓的“全局对象(global object)”。

译者注:这个章节只是由于撰写需要,简单地提了一下“declarative environment”和“object environment”的概念,对定义的介绍并不完善。经过本人的核对,我认为他对“object environment”的解释甚至是错的。如果读者想要更好地理清这些东西,建议去找一些介绍 JavaScript 词法环境的资料看一看。

2) 全局对象 global object (和内部对象 intrinsic objects)

在拥有一个像样的可以执行 JavaScript 程序的环境之后,它们还需要能够执行高级操作,包括但不限于基于平台的操作。

全局对象提供了访问内置对象(built-ins)的功能,例如各种内部对象(intrinsics)、对象(object)、API 等(无论是否特定于平台)。由于这些东西的存在,全局对象的功能变得更丰富、更全面、更有用。

你可以在浏览器中通过 window 引用到全局对象,也可以在 NodeJS 中通过 global 引用到全局对象。此外还有一个通用的 globalThis 变量,它在这两个环境中都能用来访问全局对象。

首先,我们先看一下平台无关的部分。全局对象暴露了一些内置的内部对象( built-in intrinsic objects):

  1. 值(values)(例如 undefinedInfinity 等);
  2. 函数(functions) (例如 evalparseInt 等);
  3. 构造函数(constructors) (例如 BooleanDate 等);
  4. 其他(others) (例如 JSONMath 等)

除了上面那些以外,全局对象还暴露了不同平台特有的 API。

例如浏览器中就有 fetchalertdocument 等 API。

例如 DOM 就是一套众所周知的、浏览器特有的 API。它通过全局对象暴露出来。在这里,每一个 realm 都有它唯一独立的 DOM。

我们续接上面“全局执行环境”那一章节的末尾继续讲吧。全局对象除了导出那些内置对象(built-ins)之外,它还导出“对象式环境(object environment)”下声明的内容:

// `const` 声明的东西是处于“声明式环境(declarative environment)”下的
const constant = 1;// 因此它们无法通过全局对象去访问到
console.log(window.constant); // undefined// 然而,// `var` 声明的东西是处于“对象式环境(object environment)” 下的
var variable = 2;// 因此它们可以通过全局对象去访问到
console.log(window.variable); // 2

任何平台特定的对象和 API,它们都可以通过全局对象来访问。

译者注:这句话太拗口,本人翻译不出来,机翻的逻辑也是不通顺的。所以只能退而求其次,仅保留最关键的信息,以不堵塞阅读为目标。原句在这里,有缘人看到且有兴趣的话可以帮忙翻译一下:Any platform specific objects and APIs are accessible via the global object along with all intrinsic objects and new properties declared by code.

3) JavaScript 本身

最后要提的,就是在这个 realm 的执行环境中运行的 JavaScript 代码了,它也是可以与 realm 相关联的。

对执行环境(execution environment)、全局对象(global object)或在某个 realm 下产生的任何内容进行的更改/变换/更新,也都会被关联到某个唯一特定的 realm 上。

掌握“realm”的真实概念

恭喜你🎉看完了无聊的技术定义部分。接下来开始进入“不那么严肃”的内容,一切都会顺利!

1)“现实生活”中的 Realms

我们在前面强调过,realms 是一个 JavaScript 概念,并非浏览器独有。但我接下来仍会选择基于浏览器去讨论它。

现在我们已经定义了什么是领域,是时候“一睹真容”了。

在浏览器中,默认情况下只有一个 realm,即 top main realm。这就是浏览器加载的 web 应用程序所在的 realm。

正如我们刚刚学到的,Web 应用程序位于该 realm 内,该 realm 为其提供了全局执行环境(global execution environment)、最外层作用域(outer-most scope)和全局对象(global object),这个全局对象让我们得以访问各种内部对象(intrinsic objects)、平台特定 API 等。

然而,除了那个默认的 realm 以外,web 应用程序也可以创建出新的 realm,不同的 realm 是可以共存的。并且每个新的 realm 都将拥有一份独属于自己的全局执行环境、最外层作用域、全局对象…

每个 realm 都存在于一个 agent 内, 一个 agent 下可以有多个 realm。realm 则可以有子 realm 或同级 realm。

agent 将在另一篇文章中单独介绍。现在我们只需要知道,agent 是一个实体,它向自己下辖的 realm 提供不同的资源(例如事件循环)。

译者注:在 agent 之上还有 agent cluster 的概念。它们的层级关系是这样的:agent cluster -> agent -> realm。ECMAScript 规范文档里面有详细解释了这些概念。下面的内容会涉及到这几个名词,所以先在这里介绍一下。

在浏览器中,可以通过多种方式创建 realm。它们是否是同一个 agent 的子级,取决于 realm 的性质以及它们之间的关系。

这里有些例子:

  1. 同源的两个 iframe (无论是父子关系还是兄弟关系) 将在单个 agent 下形成两个 realm。
  2. 非同源的两个 iframe (无论是父子关系还是兄弟关系) 将会在不同的两个 agent 下各自形成一个 realm。(此外,为了保持跨源站点隔离,这两个 agent 甚至也是隶属于两个运行在不同进程中的不同 agent cluster 。)
  3. top main realm 和 service worker 也属于不同的两个 agent,但是这两个 agent 属于同一个 agent cluster (web worker 也是如此)。

这些关系还决定了 realm 之间可以进行何种程度的通信。

同源 iframe 的 realm 之间共享同一个事件循环,并且可以使用 contentWindow 属性,同步且自由地访问彼此的环境:

// https://example.com
const ifr = document.createElement('iframe');
ifr.src = 'https://example.com'; // same origin
ifr.onload = () => {console.log(ifr.contentWindow.document.body);// <body></body>
}
document.body.appendChild(ifr);

但跨源 iframe 的 realm 去使用这个 API 就会受到更多的访问限制:

// https://example.com
const ifr = document.createElement('iframe');
ifr.src = '//cross.origin.com'; // cross origin
ifr.onload = () => {console.log(ifr.contentWindow.document.body);// Uncaught DOMException: Blocked a frame with origin "https://example.com" from accessing a cross-origin frame.
}
document.body.appendChild(ifr);

跨源 realm 仍然可以相互通信,但通信受到更多限制,且只能基于 postMessage() 异步 API。这在 web worker、service worker 等场景下也成立。

值得一提的是,一旦著名的 shadow realms 提案落地,很快就会出现各种有趣的补充解决方案,它们可以解决此处描述的一些限制。这是一个值得持续关注的东西!

感受一下 realm 的独特性,有助于更好理解 realm 这一概念

例如,我们加载以下网站:

<html><head></head><body><iframe id="some_iframe"></iframe></body>
</html>

这么一来,里面就会有两个不同的 realm。一个是 top main realm,另一个是 iframe 里面新建的 realm。每个 realm 都有它的唯一身份标识,具有唯一的全局对象和全局执行环境:

window === some_iframe.contentWindow // false

每个 realm 都有属于自己的一组内部对象(intrinsic objects)和基于平台的 API:

window.fetch === some_iframe.contentWindow.fetch // false
window.Array === some_iframe.contentWindow.Array // false
<html><script> window.top_array = []; </script><iframe> <script> window.top.iframe_array = []; </script> </iframe><script>// top_array 和 iframe_array 诞生于不同的 realm(所以他们的原型不是同一个对象)Object.getPrototypeOf(window.iframe_array) === Object.getPrototypeOf(window.top_array) // false</script>
</html>

原始数据类型(Primitives)则不一样,即使跨了 realm,它们也是全等的。

window.Infinity === some_iframe.contentWindow.Infinity // true

2)身份不连续性

身份不连续性(identity discontinuity)是随着 realm 的存在而出现的一种特征性的状态。根据这一特征,我们能更明显的感知到 realm 的独特之处。

译者注:identity discontinuity 是一个心理学和社会科学上会使用到的术语,在 Javascript 的一些底层知识的文档和讨论中也有广泛地使用这个词来描述一种特别的现象,但本人找不到合适的中文翻译,所以使用的是机翻的结果。

为了充分地演示这个概念,我们将使用 instanceof 运算符进行演示。

想象一下,我们有一个第三方服务,它的功能是创建一个蓝色按钮。他被以 iframe 的形式加载(不要问为什么),以便 Web 应用程序可以按如下方式使用其服务:

<html><iframe id="blue_buttons_iframe"><script>window.top.createBlueButton = function(text) {const button = document.createElement('button');button.style.color = 'blue';button.value = text;return button;};</script></iframe><body><script>const blueButton = window.createBlueButton('my blue button');if (!blueButton instanceof HTMLButtonElement) {throw new Error('blue button created does not seem to actually be a button element!');}document.body.appendChild(blueButton);</script></body>
</html>

使用 instanceof,你可以判断运算符左侧的内容是否是其右侧内容的实例。例如,由于 button 元素是 HTMLButtonElement 接口的实例,所以 document.createElement('button') instanceof HTMLButtonElement 的结果是 true,而 document.createElement('div') instanceof HTMLButtonElement 的结果是 false——因为 div 元素继承自 HTMLDivElement 而不是 HTMLButtonElement,这是显而易见的。

然而,在我们的示例中,instanceof 检查将返回 false,并且将抛出自定义错误——即使 blueButton 继承自HTMLButtonElement

这太不可思议了,是吧?出现这种情况是有原因的,虽然它确实继承自 HTMLButtonElement,但总的来说,这种情况还不足以算作 “instance of”——因为,被测试的对象必须是“生下”它的那个 realm 的接口的实例

进行 instanceof 检查的最初目的,是为了确保蓝色按钮第三方服务确实提供了按钮元素,而不是其他任何元素。但实际上蓝色按钮的来源和 HTMLButtonElement 接口的来源不是同一个realm,因此 instaceof 检查将永远返回 false

上面所描述的这个错误,是由于代码中引入了身份不连续性(identity discontinuity),这表明了 realm 及其提供的一切事物是多么的独一无二。

解决身份不连续性(identity discontinuity)不见得是容易的。在上面的示例中,将检查的代码更改为 blueButton instanceof blue_buttons_iframe.contentWindow.HTMLButtonElement 是可以解决这个问题,但这不是一个可扩展的解决方案,也不是一个便利的解决方案。

为了使一个对象成为一个接口的实例,这个对象必须来自或创建于这个接口所在的 realm。

总结

我之所以整理出这些内容,是因为我没有找到任何有用、准确、易于理解的信息,没有一些现成的信息能够向我解答 “什么是realm” “realm的定义” 这些问题。为了更深入地了解 realm 在供应链攻击和安全中的作用,首先我需要充分了解 realm,这是对我来说至关重要的。我希望这些内容对你也有用。

你可以随时在 awesome-JavaScript-realms-security 这个仓库上了解我对这个领域的研究和开发。

我还建议你更多地了解 LavaMoat 🌋 开发的工具 Snow-JS ❄️ ,以进一步了解围绕 JavaScript realm 的安全防御工作。

关于 OpenTiny

图片

OpenTiny 是一套企业级 Web 前端开发解决方案,提供跨端、跨框架、跨版本的 TinyVue 组件库,包含基于 Angular+TypeScript 的 TinyNG 组件库,拥有灵活扩展的低代码引擎 TinyEngine,具备主题配置系统TinyTheme / 中后台模板 TinyPro/ TinyCLI 命令行等丰富的效率提升工具,可帮助开发者高效开发 Web 应用。


欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~更多视频内容也可关注B站、抖音、小红书、视频号

OpenTiny 也在持续招募贡献者,欢迎一起共建

OpenTiny 官网:https://opentiny.design/

OpenTiny 代码仓库:https://github.com/opentiny/

TinyVue 源码:https://github.com/opentiny/tiny-vue

TinyEngine 源码: https://github.com/opentiny/tiny-engine

欢迎进入代码仓库 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI~

如果你也想要共建,可以进入代码仓库,找到 good first issue标签,一起参与开源贡献~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/418031.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【51单片机Keil+Proteus8.9】温室盆栽灌溉系统

实验五 实验名称 温室盆栽灌溉系统 软件设计&#xff1a; 1. 定义对应的引脚和端口的别名。 2. 编写延时函数&#xff0c;用于控制程序的执行速度。 3. 编写LCD控制函数&#xff0c;包括发送命令和发送数据两种操作。 4. 编写显示函数&#xff0c;用于在LCD上显示字符串…

免费使用IntelliJ IDEA的7种方式(2024 最新版)

大家好&#xff0c;我是小黑&#xff0c;今天要和大家分享的是如何免费使用 IntelliJ IDEA。我们都知道&#xff0c;作为一名程序员&#xff0c;拥有一个高效的开发工具是至关重要的。IntelliJ IDEA 无疑是市面上最受欢迎的开发工具之一。但是&#xff0c;获取授权的成本有时会…

探究Java中的链表

引言&#xff1a; 在Java编程中&#xff0c;链表是一种常见的数据结构&#xff0c;具有灵活的内存管理和动态的元素插入与删除能力。本篇博客将深入探讨链表的结构和概念&#xff0c;比较链表与顺序表的区别&#xff0c;介绍Java中LinkedList的常用函数并通过示例说明LinkedLis…

Android Termux技能大揭秘:安装MySQL并实现公网远程连接

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、Cpolar杂谈 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 安装MariaDB二. 安装cpolar内网穿透工具三. 创建安全隧道映射mysql四. 公网…

class_12:析构函数

#include <iostream>using namespace std;class Myclass{ private:int* datas; public:Myclass(int size){datas new int[size];}~Myclass(){cout<<"析构函数被调用"<<endl;delete [] datas;} };int main() {cout << "Hello World!&qu…

【复现】科达ViewShot登录系统数据库信息泄露漏洞_23

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 科达ViewShot视频监控系统采用数字化、网络化和智能化相融合的新一代视频监控技术&#xff0c;支持领先的视音频编解码算法&#…

x-cmd pkg | ncat - 网络调试工具

目录 简介首次用户快速实验指南通用的网络连接器强大的网络调试功能相关作品竞品进一步探索 简介 Ncat 是一个功能丰富的网络工具&#xff0c;用于在网络中读取、写入、重定向和加密数据。它可以处理各种安全测试和管理任务。 Ncat 是 Nmap 工具集的一部分&#xff0c;适合交…

国科大模式识别与机器学习2015-2019、2021、2023仅考题

2015 &#xff08;8&#xff09;试描述线性判别函数的基本概念&#xff0c;并说明既然有线性判别函&#xff0c;为什么还需要非线性判别函数&#xff1f;假设有两种模式&#xff0c;每类包括6个4维不同的模式&#xff0c;且良好分布。如果他们是线性可分的。问权向量至少需要几…

抖音小店无货源怎么做?新手常见问题解析,做抖店前认真阅读

大家好&#xff0c;我是电商花花。 要说现在线上创业项目什么最靠谱&#xff0c;那首先就是正处于红利期的抖音小店无货源电商项目。 无货源模式不需要货源&#xff0c;创业起来的成本低&#xff0c;风险低&#xff0c;深受创业者喜欢。 很多人都是看到抖音的巨大流量&#…

Linux 批量添加 known_hosts

前言 我们在做完linux ssh 免密登录后&#xff0c;通常会执行一些自动化任务&#xff08;比如启动Spark集群&#xff09;&#xff0c;也就是需要ssh到每台节点执行相同命令。但是有一个问题就是如果 known_hosts 文件中不存在这个ip的话&#xff0c;在第一次连接时会弹出确认公…

【C++入门】STL容器--vector底层数据结构剖析

目录 前言 1. vector的使用 vector的构造 vector迭代器 vector空间相关的接口 vector 功能型接口 find swap insert erase 2. vector内部数据结构剖析 reserve push_back和pop_back size、capacity、empty、operator[ ]&#xff1b; insert和erase resize swap 拷贝构造和…

Rust - 初识结构体

struct&#xff0c;或者 structure&#xff0c;是一个自定义数据类型&#xff0c;允许命名和包装多个相关的值&#xff0c;从而形成一个有意义的组合。如果你熟悉一门面向对象语言&#xff0c;struct 就像对象中的数据属性。 定义并实例化结构体 结构体和之前介绍过的元组类似…