写点什么

如何安全的运行第三方 JavaScript 代码(下)?

  • 2019-09-30
  • 本文字数:5102 字

    阅读完需:约 17 分钟

如何安全的运行第三方JavaScript代码(下)?

在本文中,我们将为读者详细介绍如何在自己的软件中安全地运行第三方 JavaScript 代码。


使用 Realms 安全地实现 API

总的来说,我们觉得 Realms 的沙箱功能还是非常不错。尽管与 JavaScript 解释器方法相比,我们要处理更多细节,但它仍然可以作为白名单而不是黑名单来运作,这使其实现代码更加紧凑,因此也更便于审计。作为另一个加分项,它还是由受人尊敬的 Web 社区成员所创建的。


但是,单靠 Realms 仍然无法满足我们的要求,因为它只是一个沙箱,插件在其中不能做任何事情。我们仍然需要实现可以供插件使用的 API。这些 API 也必须是安全的,因为大多数插件都需要能够显示一些 UI,以及发送网络请求。


例如,假设沙箱默认情况下不包含 console 对象。毕竟,console 是一个浏览器 API,而不是 JavaScript 功能。为此,我们可以将其作为全局变量传递给沙箱。


realm.evaluate(USER_CODE, { log: console.log })
复制代码


或者将原来的值隐藏在函数中,使沙箱无法修改它们:


realm.evaluate(USER_CODE, { log: (...args) => { console.log(...args) } })
复制代码


不幸的是,这是一个安全漏洞。即使在第二个示例中,匿名函数也是在 realm 之外创建的,却直接提供给了 realm。这意味着插件可以通过 log 函数的原型链逃逸到沙箱之外。


实现 console.log 的正确方法是将其封装到在 realm 内部创建的函数中。这里是一个简化版本的示例(实际上,它还需要对 realms 间抛出异常进行相应的转换处理)。


// Create a factory function in the target realm.// The factory return a new function holding a closure.const safeLogFactory = realm.evaluate(        (function safeLogFactory(unsafeLog) {                return function safeLog(...args) {                        unsafeLog(...args);                }        })); // Create a safe functionconst safeLog = safeLogFactory(console.log); // Test it, abort if unsafeconst outerIntrinsics = safeLog instanceof Function;const innerIntrinsics = realm.evaluate(log instanceof Function, { log: safeLog });if (outerIntrinsics || !innerIntrinsics) throw new TypeError(); // Use itrealm.evaluate(log("Hello outside world!"), { log: safeLog });
复制代码


一般来说,不允许沙箱直接访问在沙箱之外创建的对象,因为这些对象可以访问全局作用域。同样重要的是,应用编程接口在操作沙箱内部的对象时要格外小心,因为这可能跟沙箱外部的对象相混淆。


这就带来一个问题——虽然该方法能用于构建一个安全的应用程序接口,但是开发人员每次向应用程序接口添加一个新函数时,都需要考察对象源在语义上是否有问题。那我们该怎么解决呢?

用于解释器的 API

问题是直接利用 Realms 构建 Figma 应用编程接口的话,则需要对每个 API 端点都进行安全审计,包括其输入和输出值。很明显,这样的话,工作量实在太大了。



尽管 Realms 沙箱中的代码是使用相同的 JavaScript 引擎运行的,但如果假设我们仍然面临 WebAssembly 方法所带来的限制的话,这对我们是非常有帮助的。


回顾一下 Duktape,在尝试#2 章节中,JavaScript 解释器将被编译为 WebAssembly。因此,主线程中的 JavaScript 代码无法直接保存对沙箱内对象的引用。毕竟,在沙箱中,WebAssembly 是通过自己来管理堆的,因此,所有 JavaScript 对象都位于这个堆所在的内存空间中。事实上,Duktape 甚至可能没有使用与浏览器引擎相同的内存表示来实现 JavaScript 对象!


因此,Duktape 的 API 只能借助于低级操作实现,例如一会儿将整数和字符串复制到虚拟机中,一会儿再复制回来。即便可以在解释器中保存对象或函数的引用,但也仅能作为不透明句柄使用。


这种接口看起来像下面这样:


// vm == virtual machine == interpreterexport interface LowLevelJavascriptVm {  typeof(handle: VmHandle): string   getNumber(handle: VmHandle): number  getString(handle: VmHandle): string   newNumber(value: number): VmHandle  newString(value: string): VmHandle  newObject(prototype?: VmHandle): VmHandle  newFunction(name: string, value: (this: VmHandle, ...args: VmHandle[]) => VmHandle): VmHandle   // For accessing properties of objects  getProp(handle: VmHandle, key: string | VmHandle): VmHandle  setProp(handle: VmHandle, key: string | VmHandle, value: VmHandle): void  defineProp(handle: VmHandle, key: string | VmHandle, descriptor: VmPropertyDescriptor): void   callFunction(func: VmHandle, thisVal: VmHandle, ...args: VmHandle[]): VmCallResult  evalCode(code: string): VmCallResult} export interface VmPropertyDescriptor {  configurable?: boolean  enumerable?: boolean  get?: (this: VmHandle) => VmHandle  set?: (this: VmHandle, value: VmHandle) => void}
复制代码


请注意,这些就是 API 实现将要使用的接口,但它或多或少地以一对一的形式映射到 Duktape 的解释器 API。毕竟,Duktape(和类似的虚拟机)的构建正是为了以嵌入形式使用,并允许嵌入方与 Duktape 进行通信。


使用该接口,可以将对象{x: 10, y: 10}传递到沙箱,具体如下所示:


let vm: LowLevelJavascriptVm = createVm()let jsVector = { x: 10, y: 10 }let vmVector = vm.createObject()vm.setProp(vmVector, "x", vm.newNumber(jsVector.x))vm.setProp(vmVector, "y", vm.newNumber(jsVector.y))
复制代码


下面给出用于 Figma 节点对象的“opacity”属性的 API:


vm.defineProp(vmNodePrototype, 'opacity', {  enumerable: true,  get: function(this: VmHandle) {    return vm.newNumber(getNode(vm, this).opacity)  },  set: function(this: VmHandle, val: VmHandle) {    getNode(vm, this).opacity = vm.getNumber(val)    return vm.undefined  }})
复制代码


这个底层接口可以通过 Realms 沙箱很好地实现。这样的实现只需要相对较少的代码(就本例来说,大约为 500 LOC)。不过,我们需要对这一小部分代码进行仔细审计。但是,一旦完成了上述工作,就可以直接利用这些接口来开发其他的 API,而不用担心沙箱方面的安全问题。



从本质上讲,这就是将 JavaScript 解释器和 Realms 沙箱视为“运行 JavaScript 代码的一些独立环境”。


在沙箱上创建低级抽象还需要关注另一个关键问题。虽然我们对 Realms 的安全性充满了信心,但根据经验,在安全方面再小心也不为过。所以,我们不妨假设 Realms 中存在未知的安全漏洞,总有一天会变成我们必须处理的问题。


这就是前面花了许多章节来介绍如何编译一个甚至不用的解释器的原因。因为该 API 是通过一个其实现可以互换的接口实现的,所以,解释器仍然是一个有效的备份计划,我们可以在无需重新实现任何 API 或破坏任何现有插件的情况下启用它。

插件功能的多样性

现在,我们获得了可以安全运行任意插件的沙箱,以及允许这些插件操作 Figma 文档的 API。这就相当于为我们的世界打开了一扇大门。


但是,我们试图解决的最初问题是为设计工具构建插件系统。为提高可用性,这些插件中的大部分都需要具备创建用户界面的功能,并且许多插件还需要具有某种形式的网络访问能力。更一般地说,我们希望插件能够尽可能多地利用浏览器和 JavaScript 的生态系统。


我们可以一次一个地、小心谨慎地公开安全的、受限制的浏览器 API 版本,就像上面的 console.log 示例一样。然而,浏览器 API(尤其是 DOM)的涉及面太大,甚至比 JavaScript 本身还要大。这种尝试可能因限制太多而无法使用,或者可能存在安全缺陷。


我们通过重新引入源为 null 的来解决这个问题。这样的话,插件就可以创建一个并在其中放置任意 HTML 和 Javascript 代码了。



这跟我们最初尝试使用的的区别在于,现在,插件是由两个组件组成:


· 一个可以访问 Figma 文档并在 Realms 沙箱内的主线程上运行的组件。


· 一个可以访问浏览器 API 并在内部运行的组件。


这两个组件可以通过消息传递进行通信。虽然这种架构使得使用浏览器 API 比在同一环境中运行这两个组件要繁琐一些,但是,鉴于目前的浏览器技术的状况,这是安全地运行他人 Javascript 代码的最佳技术,当然,随着技术的进步,将来一定会出现更好的插件创建技术。

小结

经过一段曲折的探索之旅后,我们终于找到了一个实现插件的行之有效的解决方案。借助于 Realm 的 shim 库,我们不仅实现了第三方代码的隔离,同时仍然允许它在开发人员熟悉的类浏览器环境中运行。


虽然这对我们来说是最好的解决方案,但对于每个公司或平台而言,它可能并非最终之选。如果您需要隔离第三方代码,并且具有与我们相同的性能和 API 人体工程学方面的要求,那么我们的解决方案还是非常值得借鉴的;否则的话,可能直接通过 iframe 隔离代码就足够了,而且简单的方案总是上上之选。当然,我们的出发点也是冲着简单去的!(本文转自嘶吼


系列文章:


如何安全的运行第三方 JavaScript 代码(上)?


如何安全的运行第三方 JavaScript 代码(中)?


2019-09-30 14:333538

评论

发布
暂无评论
发现更多内容

加锁失效,非锁之过,加之错也

京东科技开发者

dubbo3.0 服务导入导出原理

京东科技开发者

DNS解析常见问题有哪些?DNS解析出现问题怎么解决?

国科云

DApp质押挖矿系统开发详细案例及源码部署指南

区块链软件开发推广运营

交易所开发 dapp开发 区块链开发 链游开发 代币阿凯

小程序多端引流技术上的“降本增效”

FinFish

小程序技术 小程序容器技术 小程序多端引流

如何激发下属的工作积极性

老张

团队管理 绩效管理

Web3 游戏周报(10.27 - 11.02)

Footprint Analytics

链游

AI Fabric:通往 GenAI 和数据驱动型企业的最快途径

Altair RapidMiner

人工智能 机器学习 数据分析 altair RapidMiner

NFTScan | 10.28~11.3 NFT 市场热点汇总

NFT Research

NFT\ NFTScan

快递鸟电子面单模板规格大全

快递鸟

快递 电子面单

Zypher Network:全栈式 Web3 游戏引擎,引领服务器抽象叙事

西柚子

软件测试学习笔记丨Flask操作数据库-多对多

测试人

数据库 软件测试 测试开发

华为云开源时序数据库openGemini:使用列存引擎解决时序高基数问题

华为云开发者联盟

Clickhouse 时序数据库 高基数 openGemini

鸿蒙Flutter实战:01-搭建开发环境

少湖说

flutter 鸿蒙 HarmonyOS NEXT

软件测试学习笔记丨Flask操作数据库-一对多

测试人

软件测试

Fish Agent:多语言 Voice-to-Voice 开源语音模型;Runway 推出摄像机运镜功能丨 RTE 开发者日报

声网

拼多多详情API的价值与应用解析

科普小能手

API 接口 API 测试 拼多多API接口 拼多多API 拼多多商品API接口

为什么使用海外云手机进行TikTok矩阵化运营?

Ogcloud

云手机 海外云手机 tiktok云手机 tiktok运营 TikTok矩阵运营

互联网大厂钟爱的压测工具分享

优测云服务平台

压力测试、

友商科技:热仿真分析咨询点击百万 热设计仿真公司

极客天地

快递鸟上门取件API接口代码流程

快递鸟

快递物流

一张网支撑AI端到端应用,哔哩哔哩与华为联袂演绎高维度算网融合

新消费日报

在昇腾Ascend 910B上运行Qwen2.5推理

SEAL安全

软件测试学习笔记丨Flask操作数据库-数据CRUD(增删改查)

测试人

软件测试 测试开发

项目经理把控项目质量真的很难吗?

Hi-CodeCaptain

代码质量 测试覆盖率 精准测试 软件开发、 质量内建

5 年 “0” 故障,万亿级城商行的非结构化数据底座

XSKY星辰天合

如何安全的运行第三方JavaScript代码(下)?_编程语言_Rudi Chen_InfoQ精选文章