Skip to main content

安全

¥Security

报告安全问题

有关如何正确披露 Electron 漏洞的信息,请参阅 SECURITY.md

¥For information on how to properly disclose an Electron vulnerability, see SECURITY.md.

对于上游 Chromium 漏洞:Electron 与 Chromium 的交替发布保持同步。有关详细信息,请参阅 Electron 释放时间表 文档。

¥For upstream Chromium vulnerabilities: Electron keeps up to date with alternating Chromium releases. For more information, see the Electron Release Timelines document.

前言

¥Preface

作为网络开发者,我们通常享受浏览器强大的安全网络 - 与我们编写的代码相关的风险相对较小。我们的网站在沙箱中被授予有限的权力,我们相信我们的用户喜欢由大型工程师团队构建的浏览器,该浏览器能够快速响应新发现的安全威胁。

¥As web developers, we usually enjoy the strong security net of the browser — the risks associated with the code we write are relatively small. Our websites are granted limited powers in a sandbox, and we trust that our users enjoy a browser built by a large team of engineers that is able to quickly respond to newly discovered security threats.

使用 Electron 时,重要的是要了解 Electron 不是 Web 浏览器。它允许你使用熟悉的 Web 技术构建功能丰富的桌面应用,但你的代码拥有更强大的功能。JavaScript 可以访问文件系统、用户 shell 等。这使你可以构建高质量的原生应用,但固有的安全风险会随着授予代码的额外权限而增加。

¥When working with Electron, it is important to understand that Electron is not a web browser. It allows you to build feature-rich desktop applications with familiar web technologies, but your code wields much greater power. JavaScript can access the filesystem, user shell, and more. This allows you to build high quality native applications, but the inherent security risks scale with the additional powers granted to your code.

考虑到这一点,请注意,显示来自不受信任来源的任意内容会带来严重的安全风险,而 Electron 不打算处理这种风险。事实上,最流行的 Electron 应用(Atom、Slack、Visual Studio Code 等)主要显示本地内容(或无需集成 Node 的可信、安全远程内容) - 如果你的应用从在线源执行代码,则你有责任 确保代码不是恶意的。

¥With that in mind, be aware that displaying arbitrary content from untrusted sources poses a severe security risk that Electron is not intended to handle. In fact, the most popular Electron apps (Atom, Slack, Visual Studio Code, etc) display primarily local content (or trusted, secure remote content without Node integration) — if your application executes code from an online source, it is your responsibility to ensure that the code is not malicious.

一般准则

¥General guidelines

安全是每个人的责任

¥Security is everyone's responsibility

重要的是要记住,Electron 应用的安全性是框架基础(Chromium、Node.js)、Electron 本身、所有 NPM 依赖和代码的整体安全性的结果。因此,你有责任遵循一些重要的最佳实践:

¥It is important to remember that the security of your Electron application is the result of the overall security of the framework foundation (Chromium, Node.js), Electron itself, all NPM dependencies and your code. As such, it is your responsibility to follow a few important best practices:

  • 使用最新的 Electron 框架版本使你的应用保持最新。发布产品时,你还会发布由 Electron、Chromium 共享库和 Node.js 组成的打包包。影响这些组件的漏洞可能会影响应用的安全性。通过将 Electron 更新到最新版本,你可以确保关键漏洞(例如 nodeIntegration 绕过)已得到修补,并且无法在你的应用中被利用。欲了解更多信息,请参阅“使用当前版本的 Electron”。

    ¥Keep your application up-to-date with the latest Electron framework release. When releasing your product, you’re also shipping a bundle composed of Electron, Chromium shared library and Node.js. Vulnerabilities affecting these components may impact the security of your application. By updating Electron to the latest version, you ensure that critical vulnerabilities (such as nodeIntegration bypasses) are already patched and cannot be exploited in your application. For more information, see "Use a current version of Electron".

  • 评估你的依赖。虽然 NPM 提供了 50 万个可重用包,但你有责任选择受信任的第 3 方库。如果你使用受已知漏洞影响的过时库或依赖维护不善的代码,你的应用安全可能会受到威胁。

    ¥Evaluate your dependencies. While NPM provides half a million reusable packages, it is your responsibility to choose trusted 3rd-party libraries. If you use outdated libraries affected by known vulnerabilities or rely on poorly maintained code, your application security could be in jeopardy.

  • 采用安全编码实践。应用的第一道防线是你自己的代码。常见的 Web 漏洞,例如跨站脚本 (XSS),对 Electron 应用具有较高的安全影响,因此强烈建议采用安全软件开发最佳实践并执行安全测试。

    ¥Adopt secure coding practices. The first line of defense for your application is your own code. Common web vulnerabilities, such as Cross-Site Scripting (XSS), have a higher security impact on Electron applications hence it is highly recommended to adopt secure software development best practices and perform security testing.

隔离不受信任的内容

¥Isolation for untrusted content

每当你从不受信任的来源(例如远程服务器)接收代码并在本地执行它时,就会存在安全问题。作为示例,考虑在默认 BrowserWindow 中显示的远程网站。如果攻击者以某种方式设法更改所述内容(通过直接攻击源,或者通过位于你的应用和实际目标之间),他们将能够在用户的计算机上执行原生代码。

¥A security issue exists whenever you receive code from an untrusted source (e.g. a remote server) and execute it locally. As an example, consider a remote website being displayed inside a default BrowserWindow. If an attacker somehow manages to change said content (either by attacking the source directly, or by sitting between your app and the actual destination), they will be able to execute native code on the user's machine.

警告

在任何情况下,都不应在启用 Node.js 集成的情况下加载和执行远程代码。相反,仅使用本地文件(与应用打包在一起)来执行 Node.js 代码。要显示远程内容,请使用 <webview> 标签或 WebContentsView,并确保禁用 nodeIntegration 并启用 contextIsolation

¥Under no circumstances should you load and execute remote code with Node.js integration enabled. Instead, use only local files (packaged together with your application) to execute Node.js code. To display remote content, use the <webview> tag or a WebContentsView and make sure to disable the nodeIntegration and enable contextIsolation.

Electron 安全警告

安全警告和建议将打印到开发者控制台。它们仅在二进制文件名称为 Electron 时出现,表明开发者当前正在查看控制台。

¥Security warnings and recommendations are printed to the developer console. They only show up when the binary's name is Electron, indicating that a developer is currently looking at the console.

你可以通过在 process.envwindow 对象上设置 ELECTRON_ENABLE_SECURITY_WARNINGSELECTRON_DISABLE_SECURITY_WARNINGS 来强制启用或强制禁用这些警告。

¥You can force-enable or force-disable these warnings by setting ELECTRON_ENABLE_SECURITY_WARNINGS or ELECTRON_DISABLE_SECURITY_WARNINGS on either process.env or the window object.

清单:安全建议

¥Checklist: Security recommendations

你至少应该遵循以下步骤来提高应用的安全性:

¥You should at least follow these steps to improve the security of your application:

  1. 仅加载安全内容

    ¥Only load secure content

  2. 禁用所有显示远程内容的渲染器中的 Node.js 集成

    ¥Disable the Node.js integration in all renderers that display remote content

  3. 在所有渲染器中启用上下文隔离

    ¥Enable context isolation in all renderers

  4. 启用进程沙箱

    ¥Enable process sandboxing

  5. 在加载远程内容的所有会话中使用 ses.setPermissionRequestHandler()

    ¥Use ses.setPermissionRequestHandler() in all sessions that load remote content

  6. 不要禁用 webSecurity

    ¥Do not disable webSecurity

  7. 定义一个 Content-Security-Policy 并使用限制性规则(即 script-src 'self'

    ¥Define a Content-Security-Policy and use restrictive rules (i.e. script-src 'self')

  8. 不启用 allowRunningInsecureContent

    ¥Do not enable allowRunningInsecureContent

  9. 不启用实验性功能

    ¥Do not enable experimental features

  10. 不要使用 enableBlinkFeatures

    ¥Do not use enableBlinkFeatures

  11. <webview>:不要使用 allowpopups

    ¥<webview>: Do not use allowpopups

  12. <webview>:验证选项和参数

    ¥<webview>: Verify options and params

  13. 禁用或限制导航

    ¥Disable or limit navigation

  14. 禁用或限制创建新窗口

    ¥Disable or limit creation of new windows

  15. 请勿将 shell.openExternal 与不受信任的内容一起使用

    ¥Do not use shell.openExternal with untrusted content

  16. 使用当前版本的 Electron

    ¥Use a current version of Electron

  17. 验证所有 IPC 消息的 sender

    ¥Validate the sender of all IPC messages

  18. 避免使用 file:// 协议并更喜欢使用自定义协议

    ¥Avoid usage of the file:// protocol and prefer usage of custom protocols

  19. 检查你可以更换哪些保险丝

    ¥Check which fuses you can change

要自动检测错误配置和不安全模式,可以使用 电负性。有关使用 Electron 开发应用时潜在弱点和实现错误的更多详细信息,请参阅此 开发者和审计人员指南

¥To automate the detection of misconfigurations and insecure patterns, it is possible to use Electronegativity. For additional details on potential weaknesses and implementation bugs when developing applications using Electron, please refer to this guide for developers and auditors.

1. 仅加载安全内容

¥ Only load secure content

应用中未包含的任何资源都应使用安全协议(如 HTTPS)加载。换句话说,不要使用像 HTTP 这样不安全的协议。同样,我们建议使用 WSS 而不是 WS,使用 FTPS 而不是 FTP,等等。

¥Any resources not included with your application should be loaded using a secure protocol like HTTPS. In other words, do not use insecure protocols like HTTP. Similarly, we recommend the use of WSS over WS, FTPS over FTP, and so on.

为什么?

¥Why?

HTTPS 有两个主要优点:

¥HTTPS has two main benefits:

  1. 它确保数据完整性,断言数据在应用和主机之间传输时没有被修改。

    ¥It ensures data integrity, asserting that the data was not modified while in transit between your application and the host.

  2. 它对用户和目标主机之间的流量进行加密,使窃听应用和主机之间发送的信息变得更加困难。

    ¥It encrypts the traffic between your user and the destination host, making it more difficult to eavesdrop on the information sent between your app and the host.

如何?

¥How?

main.js (Main Process)
// Bad
browserWindow.loadURL('http://example.com')

// Good
browserWindow.loadURL('https://example.com')
index.html (Renderer Process)
<!-- Bad -->
<script crossorigin src="http://example.com/react.js"></script>
<link rel="stylesheet" href="http://example.com/style.css">

<!-- Good -->
<script crossorigin src="https://example.com/react.js"></script>
<link rel="stylesheet" href="https://example.com/style.css">

2. 不要为远程内容启用 Node.js 集成

¥ Do not enable Node.js integration for remote content

信息

自 5.0.0 以来,此建议是 Electron 中的默认行为。

¥This recommendation is the default behavior in Electron since 5.0.0.

最重要的是,不要在加载远程内容的任何渲染器(BrowserWindowWebContentsView<webview>)中启用 Node.js 集成。目标是限制你授予远程内容的权力,从而使攻击者在你的用户获得在你的网站上执行 JavaScript 的能力时更难伤害你的用户。

¥It is paramount that you do not enable Node.js integration in any renderer (BrowserWindow, WebContentsView, or <webview>) that loads remote content. The goal is to limit the powers you grant to remote content, thus making it dramatically more difficult for an attacker to harm your users should they gain the ability to execute JavaScript on your website.

之后,你可以为特定主机授予附加权限。例如,如果你打开一个指向 https://example.com/ 的 BrowserWindow,你可以为该网站提供其所需的功能,但不能再多了。

¥After this, you can grant additional permissions for specific hosts. For example, if you are opening a BrowserWindow pointed at https://example.com/, you can give that website exactly the abilities it needs, but no more.

为什么?

¥Why?

如果攻击者可以跳出渲染器进程并在用户计算机上执行代码,则跨站点脚本 (XSS) 攻击会更加危险。跨站点脚本攻击相当常见 - 尽管存在问题,但它们的权力通常仅限于扰乱其执行所在的网站。禁用 Node.js 集成有助于防止 XSS 升级为所谓的 "远程代码执行" (RCE) 攻击。

¥A cross-site-scripting (XSS) attack is more dangerous if an attacker can jump out of the renderer process and execute code on the user's computer. Cross-site-scripting attacks are fairly common - and while an issue, their power is usually limited to messing with the website that they are executed on. Disabling Node.js integration helps prevent an XSS from being escalated into a so-called "Remote Code Execution" (RCE) attack.

如何?

¥How?

main.js (Main Process)
// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
nodeIntegrationInWorker: true
}
})

mainWindow.loadURL('https://example.com')
main.js (Main Process)
// Good
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(app.getAppPath(), 'preload.js')
}
})

mainWindow.loadURL('https://example.com')
index.html (Renderer Process)
<!-- Bad -->
<webview nodeIntegration src="page.html"></webview>

<!-- Good -->
<webview src="page.html"></webview>

禁用 Node.js 集成时,你仍然可以向你的网站公开使用 Node.js 模块或功能的 API。预加载脚本继续可以访问 require 和其他 Node.js 功能,允许开发者通过 上下文桥 API 向远程加载的内容公开自定义 API。

¥When disabling Node.js integration, you can still expose APIs to your website that do consume Node.js modules or features. Preload scripts continue to have access to require and other Node.js features, allowing developers to expose a custom API to remotely loaded content via the contextBridge API.

3. 启用上下文隔离

¥ Enable Context Isolation

信息

自 12.0.0 以来,此建议是 Electron 中的默认行为。

¥This recommendation is the default behavior in Electron since 12.0.0.

上下文隔离是 Electron 的一项功能,允许开发者在专用 JavaScript 上下文中的预加载脚本和 Electron API 中运行代码。实际上,这意味着像 Array.prototype.pushJSON.parse 这样的全局对象不能被渲染器进程中运行的脚本修改。

¥Context isolation is an Electron feature that allows developers to run code in preload scripts and in Electron APIs in a dedicated JavaScript context. In practice, that means that global objects like Array.prototype.push or JSON.parse cannot be modified by scripts running in the renderer process.

Electron 使用与 Chromium 的 内容脚本 相同的技术来实现此行为。

¥Electron uses the same technology as Chromium's Content Scripts to enable this behavior.

即使使用 nodeIntegration: false,为了真正实现强隔离并防止使用 Node 基础类型,也必须使用 contextIsolation

¥Even when nodeIntegration: false is used, to truly enforce strong isolation and prevent the use of Node primitives contextIsolation must also be used.

信息

有关 contextIsolation 是什么以及如何启用它的更多信息,请参阅我们的专用 上下文隔离 文档。

¥For more information on what contextIsolation is and how to enable it please see our dedicated Context Isolation document.

4. 启用进程沙箱

¥ Enable process sandboxing

沙盒 是 Chromium 的一项功能,它使用操作系统来显着限制渲染器进程可以访问的内容。你应该在所有渲染器中启用沙箱。不建议在非沙盒进程(包括主进程)中加载、读取或处理任何不受信任的内容。

¥Sandboxing is a Chromium feature that uses the operating system to significantly limit what renderer processes have access to. You should enable the sandbox in all renderers. Loading, reading or processing any untrusted content in an unsandboxed process, including the main process, is not advised.

信息

有关进程沙盒是什么以及如何启用它的更多信息,请参阅我们的专用 进程沙箱 文档。

¥For more information on what Process Sandboxing is and how to enable it please see our dedicated Process Sandboxing document.

5. 处理来自远程内容的会话权限请求

¥ Handle session permission requests from remote content

你在使用 Chrome 时可能看到过权限请求:每当网站尝试使用用户必须手动批准的功能(如通知)时,它们就会弹出。

¥You may have seen permission requests while using Chrome: they pop up whenever the website attempts to use a feature that the user has to manually approve ( like notifications).

该 API 基于 Chrom 权限 API 并实现了相同类型的权限。

¥The API is based on the Chromium permissions API and implements the same types of permissions.

为什么?

¥Why?

默认情况下,Electron 将自动批准所有权限请求,除非开发者手动配置了自定义处理程序。虽然这是一个可靠的默认设置,但具有安全意识的开发者可能希望采取相反的假设。

¥By default, Electron will automatically approve all permission requests unless the developer has manually configured a custom handler. While a solid default, security-conscious developers might want to assume the very opposite.

如何?

¥How?

main.js (Main Process)
const { session } = require('electron')
const { URL } = require('url')

session
.fromPartition('some-partition')
.setPermissionRequestHandler((webContents, permission, callback) => {
const parsedUrl = new URL(webContents.getURL())

if (permission === 'notifications') {
// Approves the permissions request
callback(true)
}

// Verify URL
if (parsedUrl.protocol !== 'https:' || parsedUrl.host !== 'example.com') {
// Denies the permissions request
return callback(false)
}
})

6. 不要禁用 webSecurity

¥ Do not disable webSecurity

信息

此建议是 Electron 的默认设置。

¥This recommendation is Electron's default.

你可能已经猜到,禁用渲染器进程(BrowserWindowWebContentsView<webview>)上的 webSecurity 属性会禁用关键的安全功能。

¥You may have already guessed that disabling the webSecurity property on a renderer process (BrowserWindow, WebContentsView, or <webview>) disables crucial security features.

不要在生产应用中禁用 webSecurity

¥Do not disable webSecurity in production applications.

为什么?

¥Why?

禁用 webSecurity 将禁用同源策略并将 allowRunningInsecureContent 属性设置为 true。换句话说,它允许执行来自不同域的不安全代码。

¥Disabling webSecurity will disable the same-origin policy and set allowRunningInsecureContent property to true. In other words, it allows the execution of insecure code from different domains.

如何?

¥How?

main.js (Main Process)
// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
webSecurity: false
}
})
main.js (Main Process)
// Good
const mainWindow = new BrowserWindow()
index.html (Renderer Process)
<!-- Bad -->
<webview disablewebsecurity src="page.html"></webview>

<!-- Good -->
<webview src="page.html"></webview>

7. 定义内容安全策略

¥ Define a Content Security Policy

内容安全策略 (CSP) 是针对跨站点脚本攻击和数据注入攻击的附加保护层。我们建议你在 Electron 中加载的任何网站都启用它们。

¥A Content Security Policy (CSP) is an additional layer of protection against cross-site-scripting attacks and data injection attacks. We recommend that they be enabled by any website you load inside Electron.

为什么?

¥Why?

CSP 允许服务内容的服务器限制和控制 Electron 可以为给定网页加载的资源。应允许 https://example.com 从你定义的源加载脚本,而不允许运行来自 https://evil.attacker.com 的脚本。定义 CSP 是提高应用安全性的简单方法。

¥CSP allows the server serving content to restrict and control the resources Electron can load for that given web page. https://example.com should be allowed to load scripts from the origins you defined while scripts from https://evil.attacker.com should not be allowed to run. Defining a CSP is an easy way to improve your application's security.

如何?

¥How?

以下 CSP 将允许 Electron 从当前网站和 apis.example.com 执行脚本。

¥The following CSP will allow Electron to execute scripts from the current website and from apis.example.com.

// Bad
Content-Security-Policy: '*'

// Good
Content-Security-Policy: script-src 'self' https://apis.example.com

CSP HTTP 标头

¥CSP HTTP headers

Electron 尊重可以使用 Electron 的 webRequest.onHeadersReceived 处理程序设置的 Content-Security-Policy HTTP 标头

¥Electron respects the Content-Security-Policy HTTP header which can be set using Electron's webRequest.onHeadersReceived handler:

main.js (Main Process)
const { session } = require('electron')

session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': ['default-src \'none\'']
}
})
})

CSP 元标记

¥CSP meta tag

CSP 的首选传送机制是 HTTP 标头。但是,当使用 file:// 协议加载资源时,无法使用此方法。在某些情况下,使用 <meta> 标签直接在标记中设置页面上的策略可能很有用:

¥CSP's preferred delivery mechanism is an HTTP header. However, it is not possible to use this method when loading a resource using the file:// protocol. It can be useful in some cases to set a policy on a page directly in the markup using a <meta> tag:

index.html (Renderer Process)
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">

8. 不启用 allowRunningInsecureContent

¥ Do not enable allowRunningInsecureContent

信息

此建议是 Electron 的默认设置。

¥This recommendation is Electron's default.

默认情况下,Electron 不允许通过 HTTPS 加载的网站加载和执行来自不安全来源的脚本、CSS 或插件 (HTTP)。将属性 allowRunningInsecureContent 设置为 true 将禁用该保护。

¥By default, Electron will not allow websites loaded over HTTPS to load and execute scripts, CSS, or plugins from insecure sources (HTTP). Setting the property allowRunningInsecureContent to true disables that protection.

通过 HTTPS 加载网站的初始 HTML 并尝试通过 HTTP 加载后续资源也称为 "混合内容"。

¥Loading the initial HTML of a website over HTTPS and attempting to load subsequent resources via HTTP is also known as "mixed content".

为什么?

¥Why?

通过 HTTPS 加载内容可确保加载资源的真实性和完整性,同时对流量本身进行加密。有关更多详细信息,请参阅有关 只显示安全内容 的部分。

¥Loading content over HTTPS assures the authenticity and integrity of the loaded resources while encrypting the traffic itself. See the section on only displaying secure content for more details.

如何?

¥How?

main.js (Main Process)
// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
allowRunningInsecureContent: true
}
})
main.js (Main Process)
// Good
const mainWindow = new BrowserWindow({})

9. 不启用实验性功能

¥ Do not enable experimental features

信息

此建议是 Electron 的默认设置。

¥This recommendation is Electron's default.

Electron 的高级用户可以使用 experimentalFeatures 属性启用实验性 Chromium 功能。

¥Advanced users of Electron can enable experimental Chromium features using the experimentalFeatures property.

为什么?

¥Why?

顾名思义,实验性功能是实验性的,尚未对所有 Chromium 用户启用。此外,它们对 Electron 整体的影响可能还没有经过测试。

¥Experimental features are, as the name suggests, experimental and have not been enabled for all Chromium users. Furthermore, their impact on Electron as a whole has likely not been tested.

存在合法的用例,但除非你知道自己在做什么,否则不应启用此属性。

¥Legitimate use cases exist, but unless you know what you are doing, you should not enable this property.

如何?

¥How?

main.js (Main Process)
// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
experimentalFeatures: true
}
})
main.js (Main Process)
// Good
const mainWindow = new BrowserWindow({})

10 . 不要使用 enableBlinkFeatures

¥. Do not use enableBlinkFeatures

信息

此建议是 Electron 的默认设置。

¥This recommendation is Electron's default.

Blink 是 Chromium 背后的渲染引擎的名称。与 experimentalFeatures 一样,enableBlinkFeatures 属性允许开发者启用默认情况下禁用的功能。

¥Blink is the name of the rendering engine behind Chromium. As with experimentalFeatures, the enableBlinkFeatures property allows developers to enable features that have been disabled by default.

为什么?

¥Why?

一般来说,如果某个功能未默认启用,可能有充分的理由。存在启用特定功能的合法用例。作为开发者,你应该确切地知道为什么需要启用某个功能、其后果是什么以及它如何影响应用的安全性。在任何情况下,你都不应推测性地启用功能。

¥Generally speaking, there are likely good reasons if a feature was not enabled by default. Legitimate use cases for enabling specific features exist. As a developer, you should know exactly why you need to enable a feature, what the ramifications are, and how it impacts the security of your application. Under no circumstances should you enable features speculatively.

如何?

¥How?

main.js (Main Process)
// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
enableBlinkFeatures: 'ExecCommandInJavaScript'
}
})
main.js (Main Process)
// Good
const mainWindow = new BrowserWindow()

11 . 不要将 allowpopups 用于 WebView

¥. Do not use allowpopups for WebViews

信息

此建议是 Electron 的默认设置。

¥This recommendation is Electron's default.

如果你使用的是 <webview>,你可能需要在 <webview> 标记中加载页面和脚本才能打开新窗口。allowpopups 属性使他们能够使用 window.open() 方法创建新的 BrowserWindows。否则不允许 <webview> 标签创建新窗口。

¥If you are using <webview>, you might need the pages and scripts loaded in your <webview> tag to open new windows. The allowpopups attribute enables them to create new BrowserWindows using the window.open() method. <webview> tags are otherwise not allowed to create new windows.

为什么?

¥Why?

如果你不需要弹出窗口,则最好默认不允许创建新的 BrowserWindows。这遵循最低要求访问的原则:不要让网站创建新的弹出窗口,除非你知道它需要该功能。

¥If you do not need popups, you are better off not allowing the creation of new BrowserWindows by default. This follows the principle of minimally required access: Don't let a website create new popups unless you know it needs that feature.

如何?

¥How?

index.html (Renderer Process)
<!-- Bad -->
<webview allowpopups src="page.html"></webview>

<!-- Good -->
<webview src="page.html"></webview>

12 . 创建前验证 WebView 选项

¥. Verify WebView options before creation

在未启用 Node.js 集成的渲染器进程中创建的 WebView 将无法启用集成本身。但是,WebView 始终会创建一个具有自己的 webPreferences 的独立渲染器进程。

¥A WebView created in a renderer process that does not have Node.js integration enabled will not be able to enable integration itself. However, a WebView will always create an independent renderer process with its own webPreferences.

最好从主进程控制新 <webview> 标签的创建,并验证其 webPreferences 不会禁用安全功能。

¥It is a good idea to control the creation of new <webview> tags from the main process and to verify that their webPreferences do not disable security features.

为什么?

¥Why?

由于 <webview> 存在于 DOM 中,因此即使 Node.js 集成被禁用,它们也可以通过在你的网站上运行的脚本来创建。

¥Since <webview> live in the DOM, they can be created by a script running on your website even if Node.js integration is otherwise disabled.

Electron 使开发者能够禁用控制渲染器进程的各种安全功能。在大多数情况下,开发者不需要禁用任何这些功能 - 因此,你不应允许新创建的 <webview> 标签进行不同的配置。

¥Electron enables developers to disable various security features that control a renderer process. In most cases, developers do not need to disable any of those features - and you should therefore not allow different configurations for newly created <webview> tags.

如何?

¥How?

在附加 <webview> 标签之前,Electron 将在托管 webContents 上触发 will-attach-webview 事件。使用该事件来防止使用可能不安全的选项创建 webViews

¥Before a <webview> tag is attached, Electron will fire the will-attach-webview event on the hosting webContents. Use the event to prevent the creation of webViews with possibly insecure options.

main.js (Main Process)
app.on('web-contents-created', (event, contents) => {
contents.on('will-attach-webview', (event, webPreferences, params) => {
// Strip away preload scripts if unused or verify their location is legitimate
delete webPreferences.preload

// Disable Node.js integration
webPreferences.nodeIntegration = false

// Verify URL being loaded
if (!params.src.startsWith('https://example.com/')) {
event.preventDefault()
}
})
})

同样,该列表只是将风险降到最低,但并没有消除风险。如果你的目标是显示网站,那么浏览器将是更安全的选择。

¥Again, this list merely minimizes the risk, but does not remove it. If your goal is to display a website, a browser will be a more secure option.

13 . 禁用或限制导航

¥. Disable or limit navigation

如果你的应用不需要导航或只需要导航到已知页面,则最好将导航完全限制在该已知范围内,而不允许任何其他类型的导航。

¥If your app has no need to navigate or only needs to navigate to known pages, it is a good idea to limit navigation outright to that known scope, disallowing any other kinds of navigation.

为什么?

¥Why?

导航是一种常见的攻击媒介。如果攻击者可以说服你的应用离开当前页面,他们可能会强制你的应用打开 Internet 上的网站。即使你的 webContents 配置为更安全(例如禁用 nodeIntegration 或启用 contextIsolation),让你的应用打开随机网站也会使利用你的应用的工作变得更加容易。

¥Navigation is a common attack vector. If an attacker can convince your app to navigate away from its current page, they can possibly force your app to open web sites on the Internet. Even if your webContents are configured to be more secure (like having nodeIntegration disabled or contextIsolation enabled), getting your app to open a random web site will make the work of exploiting your app a lot easier.

一种常见的攻击模式是,攻击者说服应用的用户与应用进行交互,从而导航到攻击者的页面之一。这通常是通过链接、插件或其他用户生成的内容来完成的。

¥A common attack pattern is that the attacker convinces your app's users to interact with the app in such a way that it navigates to one of the attacker's pages. This is usually done via links, plugins, or other user-generated content.

如何?

¥How?

如果你的应用不需要导航,你可以在 will-navigate 处理程序中调用 event.preventDefault()。如果你知道应用可能导航到哪些页面,请检查事件处理程序中的 URL,并且仅当它与你期望的 URL 匹配时才会进行导航。

¥If your app has no need for navigation, you can call event.preventDefault() in a will-navigate handler. If you know which pages your app might navigate to, check the URL in the event handler and only let navigation occur if it matches the URLs you're expecting.

我们建议你使用 Node 的 URL 解析器。简单的字符串比较有时会被欺骗 - startsWith('https://example.com') 测试会让 https://example.com.attacker.com 通过。

¥We recommend that you use Node's parser for URLs. Simple string comparisons can sometimes be fooled - a startsWith('https://example.com') test would let https://example.com.attacker.com through.

main.js (Main Process)
const { URL } = require('url')
const { app } = require('electron')

app.on('web-contents-created', (event, contents) => {
contents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl)

if (parsedUrl.origin !== 'https://example.com') {
event.preventDefault()
}
})
})

14 . 禁用或限制创建新窗口

¥. Disable or limit creation of new windows

如果你有一组已知的窗口,那么最好限制在应用中创建其他窗口。

¥If you have a known set of windows, it's a good idea to limit the creation of additional windows in your app.

为什么?

¥Why?

就像导航一样,新 webContents 的创建是一种常见的攻击媒介。攻击者试图说服你的应用创建新的窗口、框架或其他渲染器进程,并拥有比以前更多的权限;或者打开了之前无法打开的页面。

¥Much like navigation, the creation of new webContents is a common attack vector. Attackers attempt to convince your app to create new windows, frames, or other renderer processes with more privileges than they had before; or with pages opened that they couldn't open before.

如果除了你知道需要创建的窗口之外你不需要创建窗口,则禁用创建可以免费为你带来一点额外的安全性。对于打开一个 BrowserWindow 并且不需要在运行时打开任意数量的附加窗口的应用来说,这种情况很常见。

¥If you have no need to create windows in addition to the ones you know you'll need to create, disabling the creation buys you a little bit of extra security at no cost. This is commonly the case for apps that open one BrowserWindow and do not need to open an arbitrary number of additional windows at runtime.

如何?

¥How?

在创建新窗口之前,webContents 将委托给其 窗口打开处理程序。除其他参数外,处理程序还将接收请求打开窗口的 url 以及用于创建窗口的选项。我们建议你注册一个处理程序来监视窗口的创建,并拒绝任何意外的窗口创建。

¥webContents will delegate to its window open handler before creating new windows. The handler will receive, amongst other parameters, the url the window was requested to open and the options used to create it. We recommend that you register a handler to monitor the creation of windows, and deny any unexpected window creation.

main.js (Main Process)
const { app, shell } = require('electron')

app.on('web-contents-created', (event, contents) => {
contents.setWindowOpenHandler(({ url }) => {
// In this example, we'll ask the operating system
// to open this event's url in the default browser.
//
// See the following item for considerations regarding what
// URLs should be allowed through to shell.openExternal.
if (isSafeForExternalOpen(url)) {
setImmediate(() => {
shell.openExternal(url)
})
}

return { action: 'deny' }
})
})

15 . 请勿将 shell.openExternal 与不受信任的内容一起使用

¥. Do not use shell.openExternal with untrusted content

shell 模块的 openExternal API 允许使用桌面的原生实用程序打开给定的协议 URI。例如,在 macOS 上,此功能类似于 open 终端命令实用程序,将根据 URI 和文件类型关联打开特定应用。

¥The shell module's openExternal API allows opening a given protocol URI with the desktop's native utilities. On macOS, for instance, this function is similar to the open terminal command utility and will open the specific application based on the URI and filetype association.

为什么?

¥Why?

openExternal 的不当使用可能会危害用户的主机。当 openExternal 与不受信任的内容一起使用时,可以利用它来执行任意命令。

¥Improper use of openExternal can be leveraged to compromise the user's host. When openExternal is used with untrusted content, it can be leveraged to execute arbitrary commands.

如何?

¥How?

main.js (Main Process)
//  Bad
const { shell } = require('electron')
shell.openExternal(USER_CONTROLLED_DATA_HERE)
main.js (Main Process)
//  Good
const { shell } = require('electron')
shell.openExternal('https://example.com/index.html')

16 . 使用当前版本的 Electron

¥. Use a current version of Electron

你应该努力始终使用最新版本的 Electron。每当发布新的主要版本时,你应该尝试尽快更新你的应用。

¥You should strive for always using the latest available version of Electron. Whenever a new major version is released, you should attempt to update your app as quickly as possible.

为什么?

¥Why?

使用旧版本的 Electron、Chromium 和 Node.js 构建的应用比使用这些组件的更新版本的应用更容易成为目标。一般来说,旧版本 Chromium 和 Node.js 的安全问题和漏洞利用更为广泛。

¥An application built with an older version of Electron, Chromium, and Node.js is an easier target than an application that is using more recent versions of those components. Generally speaking, security issues and exploits for older versions of Chromium and Node.js are more widely available.

Chromium 和 Node.js 都是由数千名才华横溢的开发者构建的令人印象深刻的工程壮举。鉴于它们的受欢迎程度,它们的安全性由同样熟练的安全研究人员仔细测试和分析。其中许多研究人员 负责任地披露漏洞,这通常意味着研究人员在发布之前会给 Chromium 和 Node.js 一些时间来解决问题。如果你的应用运行的是最新版本的 Electron(以及 Chromium 和 Node.js),那么潜在的安全问题并不广为人知。

¥Both Chromium and Node.js are impressive feats of engineering built by thousands of talented developers. Given their popularity, their security is carefully tested and analyzed by equally skilled security researchers. Many of those researchers disclose vulnerabilities responsibly, which generally means that researchers will give Chromium and Node.js some time to fix issues before publishing them. Your application will be more secure if it is running a recent version of Electron (and thus, Chromium and Node.js) for which potential security issues are not as widely known.

如何?

¥How?

一次迁移你的应用一个主要版本,同时参考 Electron 的 重大变化 文档以查看是否需要更新任何代码。

¥Migrate your app one major version at a time, while referring to Electron's Breaking Changes document to see if any code needs to be updated.

17 . 验证所有 IPC 消息的 sender

¥. Validate the sender of all IPC messages

你应该始终验证传入的 IPC 消息 sender 属性,以确保你没有执行操作或向不受信任的渲染器发送信息。

¥You should always validate incoming IPC messages sender property to ensure you aren't performing actions or sending information to untrusted renderers.

为什么?

¥Why?

理论上所有的 Web Frame 都可以向主进程发送 IPC 消息,包括 iframe 和某些场景下的子窗口。如果你有一条 IPC 消息通过 event.reply 将用户数据返回给发送者或执行渲染器原生无法执行的特权操作,则应确保你没有监听第三方 Web 框架。

¥All Web Frames can in theory send IPC messages to the main process, including iframes and child windows in some scenarios. If you have an IPC message that returns user data to the sender via event.reply or performs privileged actions that the renderer can't natively, you should ensure you aren't listening to third party web frames.

默认情况下,你应该验证所有 IPC 消息的 sender

¥You should be validating the sender of all IPC messages by default.

如何?

¥How?

main.js (Main Process)
// Bad
ipcMain.handle('get-secrets', () => {
return getSecrets()
})

// Good
ipcMain.handle('get-secrets', (e) => {
if (!validateSender(e.senderFrame)) return null
return getSecrets()
})

function validateSender (frame) {
// Value the host of the URL using an actual URL parser and an allowlist
if ((new URL(frame.url)).host === 'electronjs.org') return true
return false
}

18 。 避免使用 file:// 协议并更喜欢使用自定义协议

¥. Avoid usage of the file:// protocol and prefer usage of custom protocols

你应该通过自定义协议而不是 file:// 协议来提供本地页面。

¥You should serve local pages from a custom protocol instead of the file:// protocol.

为什么?

¥Why?

file:// 协议在 Electron 中比在 Web 浏览器中获得更多特权,甚至在浏览器中它的处理方式也与 http/https URL 不同。使用自定义协议可以让你与经典 Web URL 行为更加一致,同时保留对可以加载的内容和时间的更多控制。

¥The file:// protocol gets more privileges in Electron than in a web browser and even in browsers it is treated differently to http/https URLs. Using a custom protocol allows you to be more aligned with classic web url behavior while retaining even more control about what can be loaded and when.

file:// 上运行的页面可以单方面访问你计算机上的每个文件,这意味着 XSS 问题可用于从用户计算机加载任意文件。使用自定义协议可以防止此类问题,因为你可以将协议限制为仅提供特定的文件集。

¥Pages running on file:// have unilateral access to every file on your machine meaning that XSS issues can be used to load arbitrary files from the users machine. Using a custom protocol prevents issues like this as you can limit the protocol to only serving a specific set of files.

如何?

¥How?

按照 protocol.handle 示例了解如何通过自定义协议提供文件/内容。

¥Follow the protocol.handle examples to learn how to serve files / content from a custom protocol.

19 . 检查你可以更换哪些 fuses

¥. Check which fuses you can change

Electron 附带了许多有用的选项,但大部分应用可能不需要。为了避免构建你自己的 Electron 版本,可以使用 保险丝 关闭或打开这些功能。

¥Electron ships with a number of options that can be useful but a large portion of applications probably don't need. In order to avoid having to build your own version of Electron, these can be turned off or on using Fuses.

为什么?

¥Why?

某些熔断器(例如 runAsNodenodeCliInspect)允许应用在使用特定环境变量或 CLI 参数从命令行运行时表现不同。这些可用于通过你的应用在设备上执行命令。

¥Some fuses, like runAsNode and nodeCliInspect, allow the application to behave differently when run from the command line using specific environment variables or CLI arguments. These can be used to execute commands on the device through your application.

这可以让外部脚本运行它们可能不被允许但你的应用可能有权执行的命令。

¥This can let external scripts run commands that they potentially would not be allowed to, but that your application might have the rights for.

如何?

¥How?

我们制作了一个模块 @electron/fuses,可以轻松翻转这些保险丝。查看该模块的自述文件,了解有关使用和潜在错误情况的更多详细信息,并参阅我们文档中的 如何翻转保险丝?

¥We've made a module, @electron/fuses, to make flipping these fuses easy. Check out the README of that module for more details on usage and potential error cases, and refer to How do I flip the fuses? in our documentation.