应用安全性
应用安全性
如果你构建的 Electron 应用目的主要显示本地内容,所有代码都是本地受信任的,即使有远程内容也是受信任的、安全的内容,那么你可以不用太在意这部分的安全性内容。
但如果你需要加载第三方不受信任来源的网站内容并且还要为这些网站提供可以访问、操作文件系统,用户等能力和权限,那么可能会造成重大的安全风险。
Electron 最大的优势是同时融入了 Node.js 和 Chromium,但这也就同时意味着 Electron 要面对来自 Web 和 Node.js 两方面的安全性问题。
Web方面安全性
先来看一下 Web 方面的安全性问题
XSS:全称是跨站脚本攻击(Cross-Site Scripting),是一种在web应用中常见的安全漏洞。它允许攻击者将恶意脚本注入到原本是可信的网站上。用户的浏览器无法判断这些脚本是否可信,因此会执行这些脚本。这可能导致用户信息被窃取、会话被劫持、网站被篡改或是被迫执行不安全的操作。
- 存储型XSS:恶意脚本被永久地存储在目标服务器上(如数据库、消息论坛、访客留言等),当用户访问这个存储了恶意脚本的页面时,脚本会被执行。
- 反射型XSS:恶意脚本在URL中被发送给用户,当用户点击这个链接时,服务器将恶意脚本作为页面的一部分返回,然后在用户浏览器上执行。这种攻击通常通过钓鱼邮件等方式实现。
- 基于DOM的XSS:这种攻击通过在客户端运行的脚本在DOM(文档对象模型)中动态添加恶意代码来实现。它不涉及向服务器发送恶意代码,而是直接在用户的浏览器中执行。
CSRF:全称是跨站请求伪造(Cross-Site Request Forgery),是一种常见的网络攻击方式。在CSRF攻击中,攻击者诱导受害者访问一个第三方网站,在受害者不知情的情况下,这个第三方网站利用受害者当前的登录状态发起一个跨站请求。这种请求可以是任何形式,如请求转账、修改密码、发邮件等,且对受害者来说是完全透明的。因为请求是在用户已经通过身份验证的会话中发起的,服务器无法区分该请求是用户自愿发起的还是被CSRF攻击所诱导的。
Node.js方面安全性
接下来我们来看一下 Node.js 方面所带来的安全性问题。
在 Electron v5 版本之前,Electorn 的架构是这样的:

其中,渲染进程和主进程之间的主要沟通桥梁是 remote 模块以及 nodeIntegration,但是在渲染进程中集成了本来只能在 Node 中使用的能力的时候,就给了攻击者一定的发挥空间,渲染进程中的恶意代码可以利用 remote 模块调用主进程的任意代码,从而控制整个应用和底层操作系统,这种安全问题是非常严重的,这在前面第一章,我们介绍 preload 的时候也有提到过。
举个例子:比如我们通过 Electron 的 BrowserWindow 模块加载了一个三方网站,然后这个网站中存在着这样的一段代码:
<img onerror="require('child_process').exec('rm -rf *')" />
这种第三方网站不受信任的代码就会造成对计算机的伤害。所以如何防止这样问题的发生,那就是不要授予这些网站直接操作 node 的能力,也就意味着遵循最小权限原则,只赋予应用程序所需的最低限度权限。
所以,从 Electron v5 开始,Electron 默认关闭这些不安全的选项,并默认选择更安全的选项。渲染器和主进程之间的通信被解耦,变得更加安全:

IPC 可用于在主进程和渲染进程之间通信,而 preload 脚本可以扩展渲染进程的功能,提供必要的操作权限,这种责任分离使我们能够应用最小权限原则。
常见措施
1. 使用preload.js和上下文隔离
在 Electron 最新版本中,默认都是关闭了渲染进程对 Node.js API 的集成,那么如果渲染进程需要使用某一些 Node.js 的 API,通过 preload.js 的方式暴露出去。
另外,新版的 Electron 还会开启一个上下文隔离,所谓上下文隔离,指的是渲染进程(网页)对应的 JS 执行环境和 Electron API 的执行环境是隔离开的,这意味着网页的 JS 是无法直接访问 Electron API 的,如果想要使用某些 API,必须通过 preload.js 暴露,然后才能使用,这也就是所谓的最小权限原则。
平时授课的时候,仅仅是为了方面,所以打开了 nodeIntegration,关闭了 contextIsolation,但是在实际开发中,同学们一定要记得通过 preload.js 去最小程度的暴露 API。
2. 开启沙盒模式
从 Electron20 版本开始,默认就会开启沙盒模式。沙盒模式的主要目录也就是起到一个主进程和渲染进程之间隔离的作用。
同样作为隔离,上下文隔离和沙盒模式的隔离会有所不同:
- sandbox:通过创建一个限制环境来隔离渲染进程,属于进程级别的隔离,相当于进程都不一样了。
- contextIsolation:分离网页内容的 JS 和 Electron 代码的执行上下文,属于上下文级别的隔离。也就是说,是属于同一个进程内容,但是通过隔离上下文的方式来防止不安全的交互。
两者之间有一个明显的区别,如果仅仅是上下文隔离,那么在 preload.js 中是能够访问 Node.js 的 API 的。
但是如果开启了沙盒模式,那么在 preload.js 里面都无法访问 Node.js 的 API 了。
另外注意,在沙盒模式下,preload.js 中仍然可以使用一部分以 polyfill 形式所提供的 Node.js API 的子集。
- events
- timers
- url
- ipcRenderer
例如在 preload.js 中:
const events = require("events");
const timers = require("timers");
const url = require("url");
console.log(events);
console.log(timers);
console.log(url);
我们可以使用这几个模块。
在沙盒模式下,如果 preload.js 里面想要使用某些 Node.js 的 API,这些 API 又不属于 polyfill 里面的,那么就需要再做一次封装,由主进程来提供相关的方法。
3. 开启webSecurity
开启该配置项之后,会启动浏览器的同源策略,一些跨域资源请求就会被拦截。
webSecurity一般也是默认开启的。
4. 限制网页的跳转
如果你的应用存在自动跳转的行为,那么我们最好将导航严格限制在特定范围内。
可以通过在 will-navigate 生命周期方法中阻止默认事件,然后再做一个白名单的校验:
const { URL } = require("url");
const { app } = require("electron");
app.on("web-contents-created", (event, contents) => {
// will-navigate 是在跳转之前会触发
contents.on("will-navigate", (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);
// 这里进行一个白名单的校验,如果跳转的地址和我预期不符合,那么我们就阻止默认事件
if (parsedUrl.origin !== "https://example.com") {
event.preventDefault();
}
});
});
最后,做一个总结,Electron 安全性的原则是基于最小化权限为前提的,也就是说,我们只为渲染进程提供最必要的权限,其他的相关操作的 API 通通不暴露出去。
另外在 Electron 官网,也总结了关于提升应用安全性的一些措施:https://www.electronjs.org/zh/docs/latest/tutorial/security