JS沙箱

178 阅读2分钟

什么是沙箱

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

顾名思义,就是让程序运行在一个隔离的环境下,不对外界的其他程序造成影响,通过创建类似沙盒的独立作业环境,在其内部运行的程序并不能对硬盘产生永久性的影响。

JS沙箱使用场景

  1. jsonp: 在解析服务器返回的jsonp数据的时候,如果不信任jsonp的数据,可以通过创建沙箱的方式来获取数据;执行第三方js(不受信任的js)的时候

  2. 在线代码编辑器: 在线编辑器中一般讲代码放在沙箱中执行,避免对页面本身造成影响

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

  4. vue 模板中的表达式: vue 模板中表达式的计算被放在沙盒中,只能访问全局变量的一个白名单,如Math和Date.你不能在模板表达式中试图访问用户定义的全局变量

总之:当解析或执行不可信的JS的时候,当需要隔离被执行代码的执行环境的时候,就可以使用沙箱了。

实现方式

1.基于标签iframe的沙箱环境实现

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

    <body> 
        <div id="root"></div> 
    </body>
  <script>
	window.onload = function() {
		const parent = window;
		const frame = document.createElement('iframe'); // 限制代码的执行能力
		frame.sandbox = 'allow-same-origin';
		const data = [1, 3, 5, 7, 9, 11, 13]
		let newData = [] // 给当前页面发送消息
		frame.onload = function(e) {
			frame.contentWindow.postMessage(data)
		}
		// iframe 对接收到的数据进行处理
		documnet.getElementById('root').appendChild(frame);
		const code = ` return dataInIframe.filter(item => item % 3 === 0) `
		frame.contentWindow.addEventListener('message', function(e) {
			console.log('iframe send message:', e.data)
			// 将计算结果发送给父页面
			const func = new frame.contentWindow.Function('dataInIframe', code)
			// 父页面接收到iframe传递的数据	
                        parent.postMessage(func(e.data)); 
                })											parent.addEventListener('message', function(e) {
                        console.log('parent - message from iframe', e.data);
		}, false); } `	
  </script>

2.于Proxy的沙箱实现

<script>
	// 根据沙箱内对属性/方法的操作记录更该window对象的属性/方法
	function updateWindowProps (prop, value, isDelete) {
		if(value === undefined || isDelete){
			// 删除属性
			delete window[prop];
		} else {
			// 更新属性
			window[prop] = value;
		}
	}
		
	// 代理沙箱实现
	class ProxySandbox {
		constructor(name) {
			// 代理沙箱名称
			this.name = name;
			// 沙箱全局对象
			const sandboxWindow = Object.create(null);
			// sandboxWindow 代理
			this.proxy = null;
			// 记录新增加的属性
			this.addedPropsMap = new Map();
			// 记录更新的属性
			this.updatedPropsMap = new Map();
			// 记录所有有更改记录的属性(新增/修改)
			this.allChangedPropsMap = new Map()
				
			const proxy = new Proxy(sandboxWindow, {
                                    get(target, prop) {
						return window[prop]
					},
					set(target, prop, value){
						if(!window.hasOwnProperty(prop)) {
						// window 对象上没有属性,记录新增
							this.addedPropsMap.set(prop, value);
						} else if(!this.updatedPropsMap.has(prop)) {
					// window 对像上已经存在的值,但是还没有更新,记录更新
							const orgVal = window[prop]
							this.updatedPropsMap.set(prop, orgVal);
						}
						// 记录所有变更的对象
						this.allChangedPropsMap.set(prop, value);
						// 更改window对象
						updateWindow(prop, value);
						return true;
					}
				});
				
				this.proxy = proxy
			}
			
			// 激活沙箱
			activeSandbox() {
				// 更新当前记录的所有属性
			       this.allChangedPropsMap.forEach((val, prop) => updateWindow(prop, val));
			}
			
			// 关闭沙箱
			inactiveSandbox() {
				// 还原所有更新过的属性
				this.updatedPropsMap.forEach((val, prop) => updateWindow(prop, val));
				// 删除所有沙箱内新添加的属性
				this.addedPropsMap.forEach((_, prop) => updateWindow(prop, undefined, true))
			}
		}
		
		// 代理沙箱测试
		const sandbox = new ProxySandbox('代理沙箱')
		const sandboxContext = sandbox.proxy
		sandboxContext.dog = '旺财'
		console.log('沙箱激活:', sandboxContext.dog, window.dog); // 旺财 旺财
		
		//关闭沙箱
		sandbox.inactiveSandbox();
		console.log('关闭沙箱:', sandboxContext.dog, window.dog); // undefined undefined
		
		// 重新激活沙箱
		sandbox.activeSandbox();
		console.log('沙箱激活:', sandboxContext.dog, window.dog); // 旺财 旺财
	</script>
    
    

3.基于diff实现

     <script>
        class DiffSandbox {
            constructor(name) {
                this.name = name
                this.modifiedProps = {}
                this.windowSnapshot = {}
            }

            activeSandbox() {
                this.windowSnapshot = {}
                for (let key in window) {
                    this.windowSnapshot[key] = window[key]
                }

                Object.keys(this.modifiedProps).forEach(propName => {
                    window[propName] = this.modifiedProps[propName]
                })
            }

            inactiveSandbox() {
                for (let key in window) {
                    if (this.windowSnapshot[key] !== window[key]) {
                        this.modifiedProps[key] = window[key]
                        window[key] = this.windowSnapshot[key]
                    }
                }
            }
        }

        // diff 沙箱测试
        const diffSandbox = new DiffSandbox('diff沙箱')
        // 激活沙箱
        diffSandbox.activeSandbox()
        window.cat = '1'
        console.log('激活沙箱', window.cat) // 1

        // 关闭沙箱
        diffSandbox.inactiveSandbox()
        console.log('关闭沙箱', window.cat) // undefined

        diffSandbox.activeSandbox()
        console.log('重新激活沙箱', window.cat) // 1
    </script>