写点什么

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

  • 2019-09-23
  • 本文字数:4438 字

    阅读完需:约 15 分钟

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

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



接上文

在主线程上运行的含义

在我们深入进行第二种尝试前,我们要先退一步,并重新考察允许插件在主线程上运行到底意味着什么。毕竟,一开始并没有考虑它,因为我们知道这可能是危险的。在主线程上运行听起来很像 eval(UNSAFE_CODE)方式。


在主线程上运行的好处是插件可以:


1.直接编辑文档而不是副本,避免加载时间问题。


2.可以运行复杂的组件更新和约束逻辑,而无需为代码置办两个副本。


3.在需要同步 API 时,可以使用同步 API 调用。这样的话,更新的加载或刷新就不会发生混淆。


4.以更直观的方式编写代码:插件只是自动执行用户可以使用 UI 手动执行的操作。


但是,这时我们又遇到了下列问题:


1.插件可挂起,但无法中断插件。


2.插件可以像 figma.com 一样发出网络请求。


3.插件可以访问和修改全局状态,例如修改 UI,甚至可以执行恶意操作,例如修改({}).proto 的值,从而危害所有新建的和现有的 JavaScript 对象。


经过斟酌后,我们决定放弃第 1 项要求。当插件被冻结时,会影响 Figma 的稳定性。然而,我们的插件模型的工作原理是,它们只处理显式的用户操作。通过在插件运行时更改 UI,冻结将始终被认为是插件所致。这也意味着插件无法“破坏”文档。


eval 的危险性体现在哪些方面?


为了解决插件能够发出网络请求和访问全局状态的问题,我们必须首先确切地了解“通过 eval 函数执行任意 JavaScript 代码是危险的”这句话到底意味着什么。


对于某些只能进行 7x24x60x60 这样的算术运算的 JavaScript 变体,我们称之为 SimpleScript,那么使用 eval 方法的话还是很安全的。


如果继续为 SimpleScript 添加其他特性,如变量赋值和 if 语句,使其更像编程语言,这时它仍然非常安全。归根结底,它本质上仍然归结为做算术。如果继续添加函数求值(function evaluation)特性,现在该语言就具备了λ演算和图灵完备性。


换句话说,JavaScript 未必一定就是危险的。在最简化的形式中,它只是一种做算术的扩展方式。真正的危险源是它的输入和输出访问权限,其中包括网络访问、DOM 访问等,即危险的是浏览器的应用程序接口。


我们知道,API 都是全局变量,因此,我们需要隐藏全局变量!


隐藏全局变量


现在,隐藏全局变量在理论上听起来不错,但仅通过“隐藏”它们来创建安全的实现还是很困难的。例如,我们可以考虑删除 window 对象的所有属性,或将它们设置为 null,但代码仍然可以访问全局值,例如({}).constructor。所以,找出泄漏全局变量值的所有可能方式是非常具有挑战性的。


相反,我们需要一些更强大的沙箱形式,使得这些全局变量值从一开始就不存在。


换句话说,JavaScript 并不一定非常危险。


考虑前面介绍的仅支持算术的 SimpleScript 语言,大家可以试着编写一个算术运算程序。在该程序的任何合理实现中,SimpleScript 将无法执行除算术之外的任何操作。


现在,我们继续扩展 SimpleScript,使其支持更多语言功能,直到它变成 JavaScript 为止,现在,我们将该程序称为解释器,它决定了 JavaScript(动态解释语言)的运行方式。

尝试 #2:将 JavaScript 解释器编译为 WebAssembly

对于像我们这样的小型创业公司来说,实现 JavaScript 编译器是不太现实的。相反,为了验证这种方法,我们采用了Duktape,这是一个用 C++编写的轻量级 JavaScript 解释器,并将其编译为 WebAssembly。


为了确认它是否有效,我们运行了test262测试,它是标准的 JavaScript 测试套件。它通过了所有 ES5 测试,只有少量不重要的测试失败了。要使用 Duktape 运行插件代码,我们需要使用编译为 WebAssembly 的解释器来调用 eval 函数。


这种方法有哪些特性?


这个解释器在主线程中运行,这意味着我们可以创建一个基于主线程的 API。


它是安全的,因为 Duktape 不支持任何浏览器 API,此外,它是作为 WebAssembly 运行的,而后者是一个无法访问浏览器 API 的沙箱环境。换句话说,默认情况下,插件代码只能通过显式的白名单 API 与外界进行通信。


它比常规 JavaScript 的速度要慢,因为这个解释器不支持 JIT,但这并不重要。


它需要浏览器编译一个中等大小的 WASM 二进制文件,这需要一些开销。


默认情况下,浏览器调试工具无法使用,但我们花了一天时间为解释器实现了一个控制台,以验证它至少可以调试插件。


Duktape 仅支持 ES5,但在 Web 社区中,通常会使用Babel等工具交叉编译较新的 JavaScript 版本。


(提示:几个月后,Fabrice Bellard 发布了QuickJS,它原生支持 ES6。)


现在,我们要编译一个 JavaScript 解释器!根据你作为程序员的爱好或审美倾向,您可能会想:


这太棒了!


或者


……这是要搞啥?还要自己搞 JavaScript 引擎,那操作系统是不是也要自己搞一个呀?


当然,这些质疑声是非常正常的! 除非我们有绝对的必要,否则最好避免重新实现浏览器。在实现整个渲染系统方面,我们花费大量的精力,因为这对于性能和跨浏览器支持来说是非常必要的,并且令人高兴的是,我们的确做到了,但我们仍然要郑重对读者说一声:不要重新发明轮子


注意,这并非我们最终采用的方法,因为后面还有更好的方法。那我们为什么要在这里介绍它呢?这是因为,这对于理解我们最终沙箱模型来说是非常有帮助的,毕竟我们的模型是非常复杂的。

尝试 #3:Realms

虽然编译 JS 解释器是一种很有前途的方法,但除此之外,还有一个方法非常需要考虑——Realms shim技术,其创建者为Agoric


这项技术将创建沙箱和支持插件描述为潜在的用例。这真是一种前途无量的描述方法!Realms API 看起来大致如下所示:


let g = window; // outer globallet r = new Realm(); // realm object let f = r.evaluate("(function() { return 17 })"); f() === 17 // true Reflect.getPrototypeOf(f) === g.Function.prototype // falseReflect.getPrototypeOf(f) === r.global.Function.prototype // true
复制代码


这种技术实际上可以使用现有的 JavaScript 特性来实现,尽管这些特性鲜为人知。沙箱的一项任务就是隐藏全局变量。这个 shim 库的核心功能大致如下所示:


function simplifiedEval(scopeProxy, userCode) {  'use strict'  with (scopeProxy) {    eval(userCode)  }}
复制代码


这是用于演示目的的简化版本;真实版本中还是有一些细微差别的。但是,它展示了其中最关键的部分:with 语句和 Proxy 对象。


其中,with(obj)语句创建了一个作用域,在该作用域内可以使用 obj 的属性查找变量。在这个例子中,我们可以将变量 PI、cos 和 sin 解析为 Math 对象的属性。另一方面,console 并不是 Math 的属性,因此需要在全局作用域内进行解析。


with (Math) {  a = PI * r * r  x = r * cos(PI)  y = r * sin(PI)  console.log(x,  y)}
复制代码


代理对象是 JavaScript 对象最动态的一种形式。


· 最基本的 JavaScript 对象可以通过访问 obj.x 返回属性的值。


· 更高级的 JavaScript 对象可以具有 getter 属性,用于返回函数的计算结果。实际上,访问 obj.x 就是调用 x 的 getter 属性。


· 代理可以通过运行函数 get 来访问任意属性。


对于下面的代理(由于它仅用于演示,所以进行了相应的简化处理)来说,当我们尝试访问它的任何属性时,都将返回 undefined,而不是对象 whitelist 中的属性值。


const scopeProxy = new Proxy(whitelist, {  get(target, prop) {    // here, target === whitelist    if (prop in target) {      return target[prop]    }    return undefined  }}
复制代码


现在,当您将这个代理用作 with 对象的参数时,它将拦截所有变量的解析过程,并且永远不会使用全局作用域来解析变量:


with (proxy) {  document // undefined!  eval("xhr") // undefined!}
复制代码


不过,这种方法仍然可以通过诸如({}).constructor 之类的表达式来访问某些全局变量。此外,沙箱也确实需要访问一些全局变量。例如,Object 是一个全局对象,并且许多合法的 JavaScript 代码(例如 Object.keys)都需要用到它。


为了让插件既能够访问这些全局变量又不会捅娄子,Realms 沙箱支持通过创建同源的 iframe 来实例化所有这些全局变量的新副本。当然,这个 iframe 不会像在尝试 #1 中那样用作沙箱。并且,同源 iframe 不会受 CORS 的限制。


相反,当在与父文档同源的情况下创建时:


1.它附带了所有全局变量的单独副本,例如 Object.prototype 等。


2.可以从父文档访问这些全局变量。



这些全局变量将被放进代理对象的“白名单”中,这样的话,插件就可以访问它们了。最后,这个新的还附带了一个新的“eval”函数副本,它与现有的函数有一个重要的区别:即使只有通过({}).constructor 这样的语法才能访问的内置值,也将会解析为 iframe 的副本。


这种基于 Realms 的沙箱方法有许多优秀的属性:


它在主线程上运行。


速度很快,因为它可以使用浏览器的 JavaScript JIT 来执行代码。


浏览器开发工具仍可以正常使用。


即使如此,我们还面临令一个非常重要的问题:这种方法安全吗?(本文转自嘶吼)


(未完待续)


系列文章


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


2019-09-23 10:592475

评论

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

云原生可观测套件:构建无处不在的可观测基础设施

阿里巴巴云原生

阿里云 云原生 可观测

WeOps赋能制造业数字化,助力坚美铝业IT高效管理

嘉为蓝鲸

DevOps 运维 AIOPS weops 嘉为蓝鲸

TiDB 的 graceful shutdown

TiDB 社区干货传送门

如何使用netlify部署vue应用程序

肥晨

11月月更 网站托管 netlift

天翼云打造自研云操作系统TeleCloudOS4.0 推动算力蓬勃发展

极客天地

共筑行业标准,亚信科技AntDB数据库参与多项数据库行业标准研讨会

亚信AntDB数据库

AntDB aisware antdb AntDB数据库

带你从0到1开发AI图像分类应用

华为云开发者联盟

人工智能 华为云 图像分类 企业号十月 PK 榜

被老板忽悠入局后,我如何在三年内让产品「起死回生」?

LigaAI

产品经理 产品管理 产品管理成功秘诀 产品负责人 企业号十月PK榜

2022前端笔试题总结

loveX001

JavaScript

经验分享|用 Flutter 如何开发一个可运行小程序的 App

FinClip

来自2年前端的面经

loveX001

JavaScript

焱融全闪系列科普|固态存储核心技术 SSD

焱融科技

云计算 分布式 高性能 文件存储 全闪存储

HDC 2022重磅首发《鸿蒙生态应用开发白皮书》,附全文

HarmonyOS开发者

HarmonyOS

从“一云多芯”支持,看多元算力的全栈云方案

华为云开发者联盟

云计算 华为云 企业号十月 PK 榜 多元算力

PingCAP 携手阿里云,探索 TiDB 云原生的进阶之路

TiDB 社区干货传送门

CSS 边框也能动画?background-origin 和 -clip 来施加魔法~

掘金安东尼

CSS 11月月更

备战双11,送你一份解压壁纸!

OceanBase 数据库

2022年11月中国数据库排行榜:GaussDB获认证进前五,GBase得融资竞逐鹿

墨天轮

数据库 opengauss TiDB 国产数据库 KingBase

前端面试指南之JS面试题总结

loveX001

JavaScript

上新丨Kyligence Zen 上线海量指标模板,轻松变身指标达人

Kyligence

数据分析 指标管理 指标中台

震惊,改密码这件小事竟然让他差点累到吐血...

嘉为蓝鲸

运维 IT #WeOps

即时通讯技术文集(第5期):零基础通信技术入门 [共15篇]

JackJiang

即时通信

带你了解NLP的词嵌入

华为云开发者联盟

人工智能 自然语言处理 华为云 企业号十月 PK 榜

盒马 iOS Live Activity &“灵动岛”配送场景实践

阿里巴巴终端技术

ios 灵动岛

号称Java圣经!Github上爆火的1058页JVM全栈小册到底有什么魅力

Java全栈架构师

程序人生 JVM 架构师 java面试 jvm调优

DDL 毫秒级同步,Light Schema Change 的设计与实现|新版本揭秘

SelectDB

数据库 大数据 Doris schema 企业号十月 PK 榜

一文了解 DataLeap 中的 Notebook

字节跳动数据平台

大数据 火山引擎 DataLeap

VUE3+TS学习-项目搭建

肥晨

Vue3 脚手架 11月月更

Curve 文件存储:如何支撑百亿级文件?

网易数帆

文件存储 分布式存储 云原生存储 curve 企业号十月 PK 榜

DevOps工具链的国产化之道

嘉为蓝鲸

DevOps 运维 IT

开源共建携手并进 OpenHarmony使能千行百业生态成果亮相HDC2022

OpenHarmony开发者

OpenHarmony

如何安全的运行第三方 JavaScript 代码(中)?_语言 & 开发_Rudi Chen_InfoQ精选文章