什么是沙箱
在计算机安全中,沙箱(Sandbox)是一种用于隔离正在运行程序的安全机制,通常用于执行未经测试或不受信任的程序或代码,它会为待执行的程序创建一个独立的执行环境,内部程序的执行不会影响到外部程序的运行。
顾名思义,就是让程序运行在一个隔离的环境下,不对外界的其他程序造成影响,通过创建类似沙盒的独立作业环境,在其内部运行的程序并不能对硬盘产生永久性的影响。
JS沙箱使用场景
-
jsonp: 在解析服务器返回的jsonp数据的时候,如果不信任jsonp的数据,可以通过创建沙箱的方式来获取数据;执行第三方js(不受信任的js)的时候
-
在线代码编辑器: 在线编辑器中一般讲代码放在沙箱中执行,避免对页面本身造成影响
-
vue 服务端渲染: vue 的服务端渲染, 通过创建沙箱执行前端的bundle文件;在调用createBundleRender方法的时候,允许配置runlnNewContext为true或false的形式,判断是否传入一个新创建的sandbox对象以供vm使用
-
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>