写点什么

如何安全的运行第三方 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:333648

评论

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

第65期 | GPTSecurity周报

云起无垠

【论文速读】|RO-SVD:一种用于 AIGC 应用的可重构硬件版权保护框架

云起无垠

RPA项目:RPA工作原理及其应用领域

八爪鱼采集器︱RPA机器人

RPA 自动化 RPAxAI

智能BI新突破,看思迈特新品背后的第二增长曲线!

ToB行业头条

神策SDK不支持Windows客户端全埋点,怎么实现用户统计分析?

ClkLog

C++客户端 C#客户端 C++客户端用户分析 C#客户端用户分析 windows客户端用户分析

荣誉+1 !入选十大科技领先成果!

天翼云开发者社区

云计算 大数据博览会

海外广告投放成功的关键点有哪些?

IPIDEA全球HTTP

是什么拖慢了医疗云桌面的部署进程?

上海锐起科技

【论文速读】| ARVO: 开源软件可重现漏洞的全景图

云起无垠

智能未来:低代码与AI如何重塑企业应用开发

天津汇柏科技有限公司

低代码 企业级应用程序开发 AI 人工智能

【行业知识】餐饮业怎么定义?需要堡垒机吗?

行云管家

等保 堡垒机 餐饮

小城酒店也要数智化,火山引擎数据飞轮能带去哪些想象?

字节跳动数据平台

大数据 数字化转型 云服务 数据平台 数据飞轮

技术分享丨使用 Knowledge Bases for Amazon Bedrock 制作 RAG 工具

伊克罗德信息科技

大数据 AWS Amazon Bedrock

《算力服务产业图谱(2024年)》发布,天翼云两款产品重磅入选!

天翼云开发者社区

云计算 云服务

类似咸鱼/转转闲置二手商品交易回收商城系统源码定制开发搭建

网站,小程序,APP开发定制

YETUADMIN 野兔二手交易系统 ThinkPHP8

好多kafka难题啊,看看其中的化解之道

王中阳Go

Go kafka 面试 面经

保定等保测评机构有吗?在哪里?

行云管家

网络安全 等保 堡垒机 保定

数据存储“取经路”,HBlock轻松“渡”!

天翼云开发者社区

云计算 存储 天翼云

在线文库网站程序系统源码/app/小程序开发定制

网站,小程序,APP开发定制

海大集团的可观测平台建设实践

巴辉特

监控 可观测性 Prometheus Nightingale Flashcat

免费试听 | AI自动化副业创收班手把手带你提升副业收益

测试人

软件测试

如何理解RPA的技术架构与原理

八爪鱼采集器︱RPA机器人

RPA 自动化 RPAxAI

从零到精通!新手实测ToDesk云电脑、易腾云、顺网云,云端畅玩黑神话悟空、魔兽世界

颜淡慕潇

游戏 云桌面

工作 6 年,@Transactional 注解用的一塌糊涂

程序员小富

Java 事务回滚

RPA是如何为企业创造价值的

八爪鱼采集器︱RPA机器人

RPA 自动化 RPAxAI

挖掘大模型行业落地潜力,火山引擎发布AI创作者大赛

极客天地

工作太忙没时间学习,怎么办?

老张

职场成长 认知 学习成长

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