【JavaScript安全】JS沙箱隔离

news/2024/11/5 0:11:04/文章来源:https://www.cnblogs.com/o-O-oO/p/18527004

原创 码中仙

一、什么是沙箱环境

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

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

例如以下几个场景:

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

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

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

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

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、微前端

典型代表是 Garfish和qiankun

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

1、 最简陋的沙箱(eval)

问题:

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

2. eval + with

问题:

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

3、 new Function + with

问题:

源程序可以访问全局变量

4. ES6 Proxy

我们先看Proxy的使用

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

get(target, propKey, receiver) :拦截对象属性的读取,比如proxy.foo和proxy['foo']。
set(target, propKey, value, receiver) :拦截对象属性的设置,比如proxy.foo = v或proxy['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 "欺骗" 程序,告诉它这个「不存在的变量」是存在的。


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


继续改造:

Symbol.unscopables:

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


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


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

仍然有很多漏洞:

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

5. iframe是天然的优质沙箱

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

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

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

6. 基于ShadowRealm 提案的实现

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

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

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

7. Web Workers

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

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

本文转自 (https://juejin.cn/post/7410347763898597388),如有侵权,请联系删除。

参考资料

[1]https://tc39.es/proposal-shadowrealm/: https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fproposal-shadowrealm%2F

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

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

相关文章

Unbound启动流程分析

unbound入口在run_daemon方法。 daemon_init方法,分配struct daemon结构体,设置信号处理方法,初始化openssl库,设置时区,设置daemon->need_to_exit为0,初始化模块栈(modstack_init方法)设置deamon->mods结构体(struct module_stack)的num为0、mod为NULL,为dae…

如何给 GoPro 拍摄的视频添加时间水印 All In One

如何给 GoPro 拍摄的视频添加时间水印 All In One 执法记录仪 时间水印是指在拍摄的照片或视频上添加一个显示时间的标识。 这个标识通常以数字的形式出现在画面的一个角落,它显示了拍摄的具体日期和时间。 时间水印的目的是为了记录下拍摄的时间,以便用户可以更好地回顾和分…

《使用Gin框架构建分布式应用》读后感

1.为什么选择这本书? 因为工作中有一个项目使用了Go进行开发,项目用到了MySQL, Redis,GORM,Rabbitmq,Zap等组件进行开发,没有使用到Web框架,因为本人是Web开发方向,所以就想了解下Web框架在Go开发中的应用,看看别人是怎么用的。国外的Go Web框架有:Echo, Gin, Iris, …

计算机组成原理基础知识(一)

来源:早期的冯诺依曼计算机以运算器为中心,现代计算机以存储器为中心; 现代计算器的结构:计算机硬件的基本组成:计算机系统的层次结构:计算机的性能指标:小练习:进制转换:

《图解设计模式》 第四部分 分开考虑

第九章 Bridge模式//使用代码 public class Main(){public static void main(String[] args){Display d1 = new Display(new StringDisPlayImpl("Hello, China."));Display d2 = new CountDisplay(new StringDisPlayImpl("Hello, World."));CountDisplay d…

Redis底层数据结构 SDS

SDS 字符串在 Redis 中是很常用的,键值对中的键是字符串类型,值有时也是字符串类型。 Redis 是用 C 语言实现的,但是它没有直接使用 C 语言的 char* 字符数组来实现字符串,而是自己封装了一个名为简单动态字符串(simple dynamic string,SDS) 的数据结构来表示字符串, 也…

PackageTracer实验中第一次Ping必然会丢包的原因

在packageTracer中做实验时发现首次ping位于不同网络中的主机时必然会超时,我对此疑惑不解,但是上网没有找到相关解答,于是我通过包跟踪找到了答案,于是将其记录下来,希望对后拉的读者有所帮助。PS:R0与R1的位置有误 PC1 Ping PC3 的过程首先,当我们在PC1发出Ping命令时…

easy-mock搭建mock平台

环境: mac 1、前提: 需要已安装node和npm(easy-mock是node.js开发的)、redis、mongodb 安装mongodb(mongodb很大,剩余内存需要10个G左右)$ brew install mongodb-atlas $ atlas setup2、安装easy-mockgit clone https://github.com/easy-mock/easy-mock.git #选择要把eas…

easy-mock搭建mock服务

环境: mac 1、前提: 需要已安装node和npm(easy-mock是node.js开发的)、redis、mongodb 安装mongodb(mongodb很大,剩余内存需要10个G左右)$ brew install mongodb-atlas $ atlas setup2、安装easy-mockgit clone https://github.com/easy-mock/easy-mock.git #选择要把eas…

[编程笔记] 奇怪的bug - 公共提示组件不生效

很简单的一个功能,初看的时候就猜到是用的某个组件,其他地方要用一般照抄,改下文案即可,这种弹出肯定是公共组件了。然鹅,很神奇的是我新做的页面,复制以后没效果!项目中有这么一个提示框:对应的代码是:top.msg.ok(123)很简单的一个功能,初看的时候就猜到是用的某个组…