sandbox是第一道防线,因其在加载前就内核级隔离执行环境,禁止子页跳转父页;必须静态声明于iframe标签且禁用allow-top-navigation,JS检测和postMessage校验均为补充手段。

iframe 里子页跳转父页,为什么 sandbox 是第一道防线
子页面用 window.top.location.href 或 document.write 触发父页跳转,本质是浏览器默认允许的导航行为。不加限制时,任何脚本都能执行它。而 sandbox 属性是唯一能在加载前就切断该能力的机制——它不是 JS 层面的补丁,而是浏览器内核级的执行环境隔离。
关键点:
-
sandbox必须写在<iframe>标签上,且不能靠 JS 动态添加(动态设置无效) - PC 端要防跳转,必须显式 **不包含**
allow-top-navigation;加上它等于开门放行 - 移动端更严格:即使没写
allow-top-navigation,只要没加allow-popups,点击链接也会静默失败(不会跳父页,但也不开新窗口) - 如果子页需要弹窗(如支付回调),
allow-popups可以保留,但绝不能搭配allow-top-navigation
为什么 window.top !== window.self 检测不能替代 sandbox
这段判断逻辑本身没错,但它只在子页 JS 能运行的前提下才起作用。而攻击者根本不需要让你的 JS 执行——比如用 srcdoc 直接注入恶意 HTML,或父页 iframe 带 sandbox=""(空沙箱),你的脚本压根不会执行,检测直接失效。
更现实的问题:
立即学习“前端免费学习笔记(深入)”;
- 检测代码必须放在
<head>最顶部,否则子页 DOM 已渲染、链接已可点击,检测就晚了 - 若父页开了
sandbox="allow-scripts allow-same-origin",你的检测能跑,但window.top.location.href = ...仍会被执行——沙箱已允许跨源脚本,也等于允许导航 - 用
try/catch包裹是必须的:某些沙箱策略下访问window.top会直接抛SecurityError,不 catch 就中断后续逻辑
postMessage 通信中 targetOrigin 和 event.origin 的坑
父页通过 postMessage 控制子页跳转(比如“跳到订单页”),子页收到后执行 window.top.location.href,这是常见模式。但这里有两个致命误区:
第一,父页发消息时 targetOrigin 填 "*" —— 这等于把指令广播给所有窗口,任意网站嵌了你的 iframe 都能伪造消息触发跳转。
第二,子页收消息时不校验 event.origin,或用 includes('mydomain.com') 这种模糊匹配 —— 攻击者域名是 https://evil-mydomain.com,照样能绕过。
正确做法:
- 父页发送时,
targetOrigin必须写死完整 origin,例如'https://shop.example.com',不能省略协议和端口 - 子页接收时,必须用
===严格比对:if (event.origin !== 'https://shop.example.com') return - 如果父页有多个合法来源(如测试域 + 生产域),需显式白名单:
['https://dev.example.com', 'https://shop.example.com'].includes(event.origin)
服务端响应头才是兜底方案
前端所有防御都可能被绕过,真正不可绕过的只有 HTTP 响应头。如果你能控制子页的服务端,优先配置:
-
X-Frame-Options: DENY:彻底禁止被嵌入,适用于独立运营的后台页 -
Content-Security-Policy: frame-ancestors 'self' https://trusted-parent.com;:只允许可信域名嵌入,比X-Frame-Options更灵活
注意:frame-ancestors 不支持 http://localhost 这类非安全上下文,本地开发需临时降级为 X-Frame-Options: SAMEORIGIN,上线前必须切回 CSP。
如果子页是第三方托管(比如 CDN 上的播放器),你无法改响应头,那就只能依赖 sandbox + postMessage 白名单双保险——没有第三条路。



















