注册
web

30分钟搞懂JS沙箱隔离

什么是沙箱环境


在计算机安全中,沙箱(Sandbox)是一种用于隔离正在运行程序的安全机制,通常用于执行未经测试或不受信任的程序或代码,它会为待执行的程序创建一个独立的执行环境,内部程序的执行不会影响到外部程序的运行。


其实在前端世界里,沙箱环境无处不在!


例如以下几个场景:



  1. Chrome本身就是一个沙箱环境

    Chrome 浏览器中的每一个标签页都是一个沙箱(sandbox)。渲染进程被沙箱(Sandbox)隔离,网页 web 代码内容必须通过 IPC 通道才能与浏览器内核进程通信,通信过程会进行安全的检查。


  2. 在线代码编辑器(码上掘金、CodeSandbox、CodePen等)

    在线代码编辑器在执行脚本时都会将程序放置在一个沙箱中,防止程序访问/影响主页面。


    image.png


  3. Vue的 服务端渲染

    在 Node.js 中有一个模块叫做 VM,它提供了几个 API,允许代码在 V8 虚拟机上下文中运行。


    const vm = require('vm');
    const sandbox = { a: 1, b: 2 };
    const script = new vm.Script('a + b');
    const context = new vm.createContext(sandbox);
    script.runInContext(context);

    vue的服务端渲染实现中,通过创建沙箱执行前端的bundle文件;在调用createBundleRenderer方法时候,允许配置runInNewContext为true或false的形式,判断是否传入一个新创建的sandbox对象以供vm使用。


  4. Figma 插件

    出于安全和性能等方面的考虑,Figma将插件代码分成两个部分:main 和 ui。其中 main 代码运行在沙箱之中,ui 部分代码运行在 iframe 之中,两者通过 postMessage 通信。


  5. 微前端

    典型代表是 Garfishqiankun


    image.png


    image.png



从0开始实现一个JS沙箱环境


1. 最简陋的沙箱(eval)


image.png


问题:



  • 要求源程序在获取任意变量时都要加上执行上下文对象的前缀
  • eval的性能问题
  • 源程序可以访问闭包作用域变量
  • 源程序可以访问全局变量

2. eval + with


image.png
问题:



  • eval的性能问题
  • 源程序可以访问闭包作用域变量
  • 源程序可以访问全局变量

3. new Function + with


image.png
问题:



  • 源程序可以访问全局变量

4. ES6 Proxy


我们先看Proxy的使用


image.png
Proxy{} 设置了属性访问拦截器,倘若访问的属性为 a 则返回 1,否则走正常程序。
Proxy 支持的拦截操作,一共 13 种:



  • get(target, propKey, receiver) :拦截对象属性的读取,比如proxy.fooproxy['foo']
  • set(target, propKey, value, receiver) :拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
  • has(target, propKey) :拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey) :拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target) :拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey) :拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc) :拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target) :拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target) :拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target) :拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto) :拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args) :拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args) :拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

在沙箱环境中,对本身不存在的变量会追溯到全局变量上访问,此时我们可以使用 Proxy "欺骗" 程序,告诉它这个「不存在的变量」是存在的。


image.png
报错了,因为我们阻止了所有全局变量的访问。


image.png
继续改造:


image.png
Symbol.unscopables


Symbol 是 JS 的第七种数据类型,它能够产生一个唯一的值,同时也具备一些内建属性,这些属性可以用来进行元编程(meta programming),即对语言本身编程,影响语言行为。其中一个内建属性 Symbol.unscopables,通过它可以影响 with 的行为,从而造成沙箱逃逸。


image.png
对这种情况做一层加固,防止沙箱逃逸



到这一步,其实很多较为简单的场景就可以覆盖了(比如: Vue 的模板字符串)。


仍然有很多漏洞:



  • code 中可以提前关闭 sandboxwith 语境,如 '} alert(this); {'
  • code 中可以使用 evalnew Function 直接逃逸
  • code 中可以通过访问原型链实现逃逸
  • 更为复杂的场景,如何实现任意使用诸如 documentlocation 等全局变量且不会影响主页面。

5. iframe是天然的优质沙箱


iframe 标签可以创造一个独立的浏览器原生级别的运行环境,这个环境由浏览器实现了与主环境的隔离。在 iframe 中运行的脚本程序访问到的全局对象均是当前 iframe 执行上下文提供的,不会影响其父页面的主体功能,因此使用 iframe 来实现一个沙箱是目前最方便、简单、安全的方法。


如果只考虑浏览器环境,可以用 With + Proxy + iframe 构建出一个比较好的沙箱:



  • 利用 iframe 对全局对象的天然隔离性,将 iframe.contentWindow 取出作为当前沙箱执行的全局对象
  • 将上述沙箱全局对象作为 with 的参数限制内部执行程序的访问,同时使用 Proxy 监听程序内部的访问。
  • 维护一个共享状态列表,列出需要与外部共享的全局状态,在 Proxy 内部实现访问控制。

image.png


6. 基于ShadowRealm 提案的实现


ShadowRealm API 是一个新的 JavaScript 提案,它允许一个 JS 运行时创建多个高度隔离的 JS 运行环境(realm),每个 realm 具有独立的全局对象和内建对象。



这项特性提案时间为 2021 年 12 月,目前在Stage 3阶段 tc39.es/proposal-sh…





  • evaluate(sourceText: string) 同步执行代码字符串,类似 eval()
  • importValue(specifier: string, bindingName: string) 异步执行代码字符串

7. Web Workers


Web Workers代码运行在独立的进程中,通信是异步的,无法获取当前程序一些属性或共享状态,且有一点无法不支持 DOM 操作,必须通过 postMessage 通知 UI 主线程来实现。



以上就是实现JS沙箱隔离的一些思考点。在真实的业务应用中,没有最完美的方案,只有最合适的方案,还需要结合自身业务的特性做适合自己的选型。


作者:木华
来源:juejin.cn/post/7410347763898597388

0 个评论

要回复文章请先登录注册