安全预览外部网站——代理 + 净化 + iframe sandbox 的实现
- engineering
- security
- nextjs
- iframe
本文要点
- 将外部网站加载到自有域名的 iframe 中时,外部 JS 有可能触碰到自身会话,存在安全隐患
- 应对方法是“带 SSRF 防护的代理 + 移除外部 JS + 限制性 CSP + iframe sandbox”的多层防御
- sandbox 设置过严会连自家脚本也一并挡住,忘记添加
allow-scripts就是一个典型陷阱
HeatMapX 的 A/B 测试中有一个功能:将“修改后(Variant B)的效果”应用到目标页面上进行预览。从技术角度看,这实际上是“在自有域名的 iframe 内安全地展示用户的外部网站”,处理起来相当微妙。下面分享一下设计思路,以及实际踩过的坑。
为什么简单实现是危险的
如果直接获取外部网站的 HTML 并在自有域名(例如 heatmapx.com)下原样提供,那么该 HTML 中包含的 <script> 或 onclick= 等代码就会 以自有域名的权限被执行。这样一来,Cookie 和登录会话就有被读取的风险。这是必须避免的情况。
多层防御的构成
因此,我们叠加了以下几层防护。
1. 带 SSRF 防护的代理
对目标 URL 进行校验,拒绝 http/https 以外的协议,并进一步屏蔽 localhost、私有 IP 以及云平台的元数据接口(如 169.254.169.254 等)。同时不自动跟随重定向,而是通过 redirect: 'manual' 逐段重新校验,防止被引导至内部网络。
2. 移除外部 JS(净化)
从获取到的 HTML 中移除 <script> 标签、on*= 内联事件处理器以及 javascript: 协议。因为预览并不需要执行客户网站的 JS(无法完全还原 SPA 是我们已知并接受的取舍)。
3. 限制性 CSP 与 base href
在 <head> 之后插入 <base href>,用于解析相对路径的图片和 CSS,同时在响应中附加“不执行外部 JS”的限制性 CSP。图片和 CSS 出于展示需要予以放行。
4. iframe sandbox
最后,为展示用的 iframe 添加 sandbox 属性进行隔离。
踩过的坑:sandbox 设置过严
预览功能的原理是:向获取到的 HTML 中注入一段“应用变更集(changeSet)”的自有小脚本,并在 iframe 内执行以改变页面效果。然而,预览用 iframe 的 sandbox 属性只设置了 allow-same-origin,却没有添加 allow-scripts。
结果就是自家脚本也无法执行,导致出现 变更未生效、仍显示原始页面 的问题。而用于编辑的编辑器侧 iframe 使用的是 allow-scripts allow-same-origin,运行正常,可见问题根源在于两者配置不一致。
修复方式很简单:让预览侧的配置也统一为 allow-scripts allow-same-origin。同时,我们新增了一项回归测试,用于校验 iframe 的 sandbox 属性。
sandbox="allow-same-origin" → sandbox="allow-scripts allow-same-origin"
经验总结
- 嵌入外部内容时要以“多层防御”的思路考虑,不要依赖单一防护手段。
sandbox并非“加上就安全”,关键在于 精准地只放行必要的权限。设置过严会连自身功能都一并挡住。- 对于“执行相同处理的两条路径”(编辑器与预览),务必确认它们的安全属性是否一致。只修好其中一处,另一处就可能出问题。
总结
外部网站的安全预览,可以通过 SSRF 防护、移除外部 JS、限制性 CSP、iframe sandbox 这一多层防御体系来实现。而 sandbox 的权限设置,无论过少还是过多都会带来问题。这次忘记添加 allow-scripts 正是一个典型案例。