为了安全,浏览器提供了很多安全机制,比如沙箱,渲染器进程其实也是一个沙箱,里面跑着js、css、html代码,渲染器进程通过IPC跟其它进程之间通讯,比如网络进程通过IPC把数据传递到渲染进程,当我们执行我们不了解的js代码时,我们需要一个安全的执行环境来保障执行的js代码不会危害到应用本身,就是经常说的js沙箱机制
使用with+new Function+proxy代理的方式来实
// js的沙箱机制,使用new Function函数执行
var out = "global"
// 使用new Function+with实现沙箱
function compileCode(code) {
return new Function("obj", `with(obj) { ${code} }`)
}
// 创建沙箱
function createSandbox(code) {
let current = {
name: "may",
obj: {
age: 18
}
}
let obj = proxyObj(current)
compileCode(code).call(obj, obj)
}
// 通过proxy的has监听属性的访问
function proxyObj(obj) {
let proxyObj = new Proxy(obj, {
has: (target, key) => {
// 如果代码访问中有"console", "Math", "Date"全局变量给予放行
if (["console", "Math", "Date"].indexOf(key) >= 0) {
return target[key]
}
// 访问的属性不在对象中,抛出异常
if (!target.hasOwnProperty(key)) {
throw new Error(key)
}
return target[key]
}
})
return proxyObj
}
createSandbox(`console.log(name);console.log(out)`)
最后的输出结果
console.log(name) 输出了may,因为may在传入进去的current中
console.log(out) 页面报错了,
虽然全局变量中out变量,但是在current中没有对应out属性,触发了Proxy中的has函数中的,异常,成功劫持了访问全局变量
// 访问的属性不在对象中,抛出异常
if (!target.hasOwnProperty(key)) {
throw new Error(key)
}
whit+proxy+Function可以实现简单的沙箱机制,在proxy进行代理拦截,例如以下在沙箱中执行获取cookie的操作
createSandbox(`console.log(name);console.log(document.cookie)`)
因为document没有放在白名单中放行,所以在使用document时会报错
在使用proxy还发现一个神奇的问题,var会出发has,但是let,const不会触发,不知道是什么原因,比如执行以以下代码不会报错
createSandbox(`let age = 23;const nationality="china";console.log(age);console.log(nationality); console.log(name);`)
但是里面使用var命名后就会触发proxy的has函数,把 let换成var就报错了
createSandbox(`var age = 23;var nationality="china";console.log(age);console.log(nationality); console.log(name);`)
使用Function+whit还是有漏洞,不如直接在沙箱中访问obj.age.__proto__是不会触发prxoy的has方法,
这样就可以直接重写原型上的方法,比如以下
在沙箱外层执行toString方法后就会实现XSS攻击
以下代码也会绕过沙箱
createSandbox(`console.log(a);console.log(document.cookie); new (() => { }).constructor("out ='inner';console.log(out);console.log(this)")()`)
这个还是有一定的漏洞
使用iframe
iframe有着天然的优势,会起到css,js的完全隔离,通过postMessage传递数据 起一个3000的主应用平台,里面嵌入一个iframe,访问3001的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>host主应用</title>
</head>
<body>
<h6>主应用</h6>
<iframe src="http://localhost:3001/sandboxIframe.html" id="pluginBox" name="pluginBox" width="100%" height="800px"
sandbox="allow-scripts allow-same-origin" frameborder="0"></iframe>
</body>
<script>
let name = "host"
window.addEventListener('message', messageHandler);
function messageHandler(e) {
console.log("接受到子级传递上来信息", e)
}
var iframe = document.getElementById('pluginBox');
iframe.onload = function () {
iframe.contentWindow.postMessage("132132", '*');
}
</script>
</html>
通过contentWindow.postMessage 传递信息到嵌入的iframe中 嵌入的iframe页面内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>沙箱iframe</title>
</head>
<body>
<p>嵌入的沙箱执行环境</p>
</body>
<script>
(function () {
window.addEventListener('message', messageHandler);
function messageHandler(e) {
console.log("接受到父级信息", e)
window.top.postMessage("子级发送消息到父级", '*');
}
})()
</script>
</html>
这样在嵌入的iframe就无法访问主应用的数据,比如在iframe修改主应用中的变量就会报跨域问题
这样就达到了沙箱的作用