问题描述

公司有个内部项目是用 Electron 来开发的,有个功能需要像浏览器一样加载第三方站点。

本来一切安好,但是某天打开某个站点的链接,导致 整个客户端直接变成了该站点的页面

这一看就是该站点做了特殊的处理,经排查网页源码后,果然发现了有这么一句代码。

 1    if (window.top !== window.self) {
 2      window.top.location = window.location;
 3    }

翻译一下就是:如果当前窗口不是顶级窗口的话,将当前窗口设置为顶级窗口。

奇怪的是两者不是 跨域 了吗,为什么 iframe 还可以影响顶级窗口。

先说一下我当时的一些解决办法:

  1. webview 替换 iframe
  2. iframe 添加 sandbox 属性

后续内容就是一点复盘工作。

场景复现(Web端)

一开始怀疑是客户端的问题,所以我用在纯 Web 上进行了一次对比验证。

这里我们新建两个文件:1.html2.html,我们称之为 页面A页面B

然后起了两个本地服务器来模拟同源与跨域的情况。

页面A:http://127.0.0.1:5500/1.html

页面B:http://127.0.0.1:5500/2.htmlhttp://localhost:3000/2.html

符合同源策略

 1<body>
 2  <h1>这是页面A</h1>
 3  
 4  <iframe id="iframe" src="http://127.0.0.1:5500/2.html" />
 5  
 6  <script>
 7    iframe.onload = () => {
 8      console.log('iframe loaded..')
 9      console.log('子窗口路径', iframe.contentWindow.location.href)
10    }
11  </script>
12</body>
 1<body>
 2  <h2>这是页面B</h2>
 3
 4  <script>
 5    console.log('page2...')
 6    console.log(window === window.top)
 7    console.log('顶部窗口路径', window.top.location.href)
 8  </script>
 9</body>

我们打开控制台可以看到 页面A 和 页面B 是可以 互相访问 到对方窗口的路径。

如果这个时候在 页面B 加上文章开头提到的 代码片段,那么显然页面将会发生变化。

跨域的情况

这时候我们修改 页面A 加载 页面B 的地址,使其不符合同源策略。

理所应当的是,两个页面不能够相互访问了,这才是正常的,否则内嵌第三方页面可以互相修改,那就太不安全了。

场景复现(客户端)

既然 Web 端是符合预期的,那是不是 Electron 自己的问题呢?

我们通过 electron-vite 快速搭建了一个 React模板的electron应用,版本为:electron@22.3.27,并且在 App 中也嵌入了刚才的 页面B。

 1function App(): JSX.Element {
 2  return (
 3    <>
 4      <h1>这是Electron页面</h1>
 5      <iframe id="iframe" src="http://localhost:3000/2.html"/>
 6    </>
 7  )
 8}
 9export default App

对不起,干干净净的 Electron 根本不背这个锅,在它身上的表现如同 Web端 一样,也受同源策略的限制。

那么肯定是我的项目里有什么特殊的配置,通过对比主进程的代码,答案终于揭晓。

 1new BrowserWindow({
 2    ...,
 3    webPreferences: {
 4        ...,
 5        webSecurity: false 
 6    }
 7})

Electron 官方文档 里是这么描述 webSecurity 这个配置的。

webSecurity boolean (可选) - 当设置为 false, 它将禁用同源策略 (通常用来测试网站), 如果此选项不是由开发者设置的,还会把 allowRunningInsecureContent设置为 true. 默认值为 true

也就是说,Electron本身是有一层屏障的,但当该属性设置为 false 的时候,我们的客户端将会绕过同源策略的限制,这层屏障也就消失了,因此 iframe 的行为表现得像是嵌套了同源的站点一样。

解决方案

把这个配置去掉,确实是可以解决这个问题,但考虑到可能对其他功能造成的影响,只能采取其他方案。

如文章开头提到的,用 webview 替换 iframe

webviewElectron的一个自定义元素(标签),可用于在应用程序中嵌入第三方网页,它默认开启安全策略,直接实现了主应用与嵌入页面的隔离。

因为目前这个需求是仅作展示,不需要与嵌套页面进行交互以及复杂的通信,因此在一开始的开发过程中,并没有使用它,而是直接采用了 iframe

iframe 也能够实现类似的效果,只需要添加一个 sandbox 属性可以解决。

MDN 中提到,sandbox 控制应用于嵌入在 <iframe> 中的内容的限制。该属性的值可以为空以应用所有限制,也可以为空格分隔的标记以解除特定的限制。

如此一来,就算是同源的,两者也不会互相干扰。

总结

这不是一个复杂的问题,发现后及时修复了,并没有造成很大的影响(还好是自己人用的平台)。

写这篇文章的主要目的是为了记录这次事件,让我意识到在平时开发过程中,把注意力过多的放在了 业务样式性能等这些看得见的问题上,可能很少关注甚至忽略了 安全 这一要素,以为前端框架能够防御像 XSS 这样的攻击就能安枕无忧。

谨记,永远不要相信第三方,距离产生美。

如有纰漏,欢迎在评论区指出。

个人笔记记录 2021 ~ 2025