跳到主要內容

安全性

回報安全性問題

如需了解如何正確揭露 Electron 漏洞的資訊,請參閱 SECURITY.md

如需上游 Chromium 漏洞的資訊:Electron 會與交替的 Chromium 版本保持同步。如需更多資訊,請參閱 Electron 版本發布時間軸文件。

前言

身為網頁開發人員,我們通常享有瀏覽器強大的安全保護網 — 我們撰寫程式碼所帶來的風險相對較小。我們的網站在沙箱中被授予有限的權限,而且我們相信使用者使用的瀏覽器是由一個龐大的工程師團隊所建置,能夠快速回應新發現的安全威脅。

當使用 Electron 時,請務必了解 Electron 並非網頁瀏覽器。它允許您使用熟悉的網頁技術建置功能豐富的桌面應用程式,但您的程式碼具有更大的權限。JavaScript 可以存取檔案系統、使用者 Shell 等。這允許您建置高品質的原生應用程式,但內在的安全風險會隨著授予您程式碼的額外權限而增加。

請記住,顯示來自不受信任來源的任意內容會構成嚴重的安全風險,而 Electron 並非旨在處理此問題。事實上,最受歡迎的 Electron 應用程式(Atom、Slack、Visual Studio Code 等)主要顯示本機內容(或受信任、安全的遠端內容,不含 Node 整合) — 如果您的應用程式執行來自線上來源的程式碼,您有責任確保程式碼並非惡意。

一般準則

安全性是每個人的責任

請務必記住,您的 Electron 應用程式的安全性是框架基礎 (ChromiumNode.js)、Electron 本身、所有 NPM 相依性和您程式碼的整體安全性所造成的結果。因此,您有責任遵循一些重要的最佳實務

  • 讓您的應用程式保持在最新的 Electron 框架版本。 當發布您的產品時,您也會發布一個由 Electron、Chromium 共用程式庫和 Node.js 組成的套件。影響這些元件的漏洞可能會影響您應用程式的安全性。透過將 Electron 更新到最新版本,您可以確保已修補關鍵漏洞(例如 nodeIntegration 繞過),而且不會在您的應用程式中被利用。如需更多資訊,請參閱「使用目前版本的 Electron」。

  • 評估您的相依性。 雖然 NPM 提供 50 萬個可重複使用的套件,但您有責任選擇受信任的第三方程式庫。如果您使用受已知漏洞影響的過時程式庫,或是依賴維護不佳的程式碼,您的應用程式安全性可能會受到危害。

  • 採用安全的程式碼撰寫實務。 應用程式的第一道防線是您自己的程式碼。常見的網頁漏洞(例如跨網站指令碼 (XSS))對 Electron 應用程式的安全性影響較大,因此強烈建議採用安全的軟體開發最佳實務並執行安全性測試。

針對不受信任內容的隔離

每當您從不受信任的來源(例如遠端伺服器)接收程式碼並在本地執行時,就會存在安全性問題。例如,假設在預設的 BrowserWindow 中顯示遠端網站。如果攻擊者以某種方式設法變更該內容(無論是直接攻擊來源,還是坐在您的應用程式和實際目的地之間),他們將能夠在使用者電腦上執行原生程式碼。

警告

在任何情況下,您都不應在啟用 Node.js 整合的情況下載入和執行遠端程式碼。相反地,請只使用本機檔案(與您的應用程式封裝在一起)來執行 Node.js 程式碼。若要顯示遠端內容,請使用 <webview> 標籤或 WebContentsView,並務必停用 nodeIntegration 並啟用 contextIsolation

Electron 安全性警告

安全性警告和建議會列印到開發人員主控台。只有在二進位檔的名稱為 Electron 時,才會顯示這些警告,表示開發人員目前正在查看主控台。

您可以透過在 process.envwindow 物件上設定 ELECTRON_ENABLE_SECURITY_WARNINGSELECTRON_DISABLE_SECURITY_WARNINGS 來強制啟用或強制停用這些警告。

檢查清單:安全性建議

您至少應遵循下列步驟來改善應用程式的安全性

  1. 僅載入安全內容
  2. 停用所有顯示遠端內容的渲染器中的 Node.js 整合
  3. 在所有渲染器中啟用內容隔離
  4. 啟用處理程序沙箱
  5. 在所有載入遠端內容的工作階段中使用 ses.setPermissionRequestHandler()
  6. 請勿停用 webSecurity
  7. 定義 Content-Security-Policy 並使用限制性規則(即 script-src 'self'
  8. 請勿啟用 allowRunningInsecureContent
  9. 請勿啟用實驗性功能
  10. 請勿使用 enableBlinkFeatures
  11. <webview>:請勿使用 allowpopups
  12. <webview>:驗證選項和參數
  13. 停用或限制導覽
  14. 停用或限制建立新視窗
  15. 請勿將 shell.openExternal 與不受信任的內容搭配使用
  16. 使用目前版本的 Electron
  17. 驗證所有 IPC 訊息的 sender
  18. 避免使用 file:// 通訊協定,並偏好使用自訂通訊協定
  19. 檢查您可以變更哪些熔斷器

若要自動偵測錯誤配置和不安全的模式,可以使用 Electronegativity。如需更多有關使用 Electron 開發應用程式時的潛在弱點和實作錯誤的詳細資訊,請參閱這份開發人員和稽核人員指南

1. 僅載入安全內容

任何未包含在應用程式中的資源都應使用安全通訊協定(例如 HTTPS)載入。換句話說,請勿使用不安全的通訊協定,例如 HTTP。同樣地,我們建議使用 WSS 而非 WS、使用 FTPS 而非 FTP 等等。

為什麼?

HTTPS 有兩個主要優點

  1. 它可確保資料完整性,斷言資料在應用程式與主機之間傳輸時未被修改。
  2. 它會加密使用者與目的地主機之間的流量,讓竊聽應用程式與主機之間傳送的資訊更加困難。

如何?

main.js (主要處理程序)
// Bad
browserWindow.loadURL('http://example.com')

// Good
browserWindow.loadURL('https://example.com')
index.html (渲染器處理程序)
<!-- 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 整合

資訊

自 5.0.0 起,此建議是 Electron 中的預設行為。

請務必不要在任何載入遠端內容的渲染器 (BrowserWindowWebContentsView<webview>) 中啟用 Node.js 整合。目標是限制您授予遠端內容的權限,如此一來,如果攻擊者取得在您的網站上執行 JavaScript 的能力,他們要傷害使用者就會變得困難許多。

在此之後,您可以為特定主機授予額外權限。例如,如果您要開啟指向 https://example.com/ 的 BrowserWindow,您可以給予該網站其所需的確切功能,但不能再多。

為什麼?

跨站腳本 (XSS) 攻擊如果能讓攻擊者跳出渲染器進程,並在使用者電腦上執行程式碼,會更加危險。跨站腳本攻擊相當常見,雖然是個問題,但它們的影響通常僅限於干擾執行它們的網站。停用 Node.js 整合有助於防止 XSS 升級為所謂的「遠端程式碼執行」(RCE) 攻擊。

如何運作?

main.js (主要處理程序)
// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
nodeIntegrationInWorker: true
}
})

mainWindow.loadURL('https://example.com')
main.js (主要處理程序)
// Good
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(app.getAppPath(), 'preload.js')
}
})

mainWindow.loadURL('https://example.com')
index.html (渲染器處理程序)
<!-- Bad -->
<webview nodeIntegration src="page.html"></webview>

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

當停用 Node.js 整合時,您仍然可以向您的網站公開 API,這些 API 會使用 Node.js 模組或功能。預載腳本仍然可以存取 require 和其他 Node.js 功能,讓開發人員可以透過 contextBridge API,向遠端載入的內容公開自訂 API。

3. 啟用內容隔離

資訊

此建議是 Electron 從 12.0.0 版本開始的預設行為。

內容隔離是 Electron 的一項功能,可讓開發人員在專用的 JavaScript 環境中執行預載腳本和 Electron API 中的程式碼。實際上,這表示像 Array.prototype.pushJSON.parse 之類的全局物件不能被在渲染器進程中執行的腳本修改。

Electron 使用與 Chromium 的 內容腳本相同的技術來啟用此行為。

即使使用 nodeIntegration: false,為了真正執行強隔離並防止使用 Node 原語,也**必須**使用 contextIsolation

資訊

如需更多關於 contextIsolation 是什麼以及如何啟用它的資訊,請參閱我們專用的 內容隔離 文件。

4. 啟用進程沙箱

沙箱是 Chromium 的一項功能,它使用作業系統來大幅限制渲染器進程可以存取的內容。您應該在所有渲染器中啟用沙箱。不建議在未沙箱化的進程中載入、讀取或處理任何不受信任的內容,包括主進程。

資訊

如需更多關於進程沙箱是什麼以及如何啟用它的資訊,請參閱我們專用的 進程沙箱 文件。

5. 處理來自遠端內容的工作階段權限請求

您在使用 Chrome 時可能看過權限請求:每當網站嘗試使用使用者必須手動批准的功能 (例如通知) 時,就會彈出這些請求。

此 API 基於 Chromium 權限 API,並實作相同類型的權限。

為什麼?

預設情況下,除非開發人員已手動設定自訂處理常式,否則 Electron 會自動批准所有權限請求。雖然這是個穩固的預設值,但有安全意識的開發人員可能希望假設完全相反的情況。

如何運作?

main.js (主要處理程序)
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

資訊

此建議是 Electron 的預設值。

您可能已經猜到,停用渲染器進程 (BrowserWindowWebContentsView<webview>) 上的 webSecurity 屬性會停用重要的安全功能。

請勿在生產應用程式中停用 webSecurity

為什麼?

停用 webSecurity 將停用同源政策,並將 allowRunningInsecureContent 屬性設定為 true。換句話說,它允許執行來自不同網域的不安全程式碼。

如何運作?

main.js (主要處理程序)
// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
webSecurity: false
}
})
main.js (主要處理程序)
// Good
const mainWindow = new BrowserWindow()
index.html (渲染器處理程序)
<!-- Bad -->
<webview disablewebsecurity src="page.html"></webview>

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

7. 定義內容安全政策

內容安全政策 (CSP) 是針對跨站腳本攻擊和資料注入攻擊的額外保護層。我們建議任何您在 Electron 中載入的網站都應啟用它們。

為什麼?

CSP 允許提供內容的伺服器限制和控制 Electron 可以為給定網頁載入的資源。應允許 https://example.com 從您定義的來源載入腳本,但不應允許執行來自 https://evil.attacker.com 的腳本。定義 CSP 是提高應用程式安全性的簡單方法。

如何運作?

以下 CSP 將允許 Electron 從目前的網站和 apis.example.com 執行腳本。

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

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

CSP HTTP 標頭

Electron 遵循 Content-Security-Policy HTTP 標頭,可以使用 Electron 的 webRequest.onHeadersReceived 處理常式設定

main.js (主要處理程序)
const { session } = require('electron')

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

CSP meta 標籤

CSP 的首選傳遞機制是 HTTP 標頭。但是,當使用 file:// 協定載入資源時,無法使用此方法。在某些情況下,可以使用 <meta> 標籤,直接在標記中設定頁面的原則。

index.html (渲染器處理程序)
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">

8. 不要啟用 allowRunningInsecureContent

資訊

此建議是 Electron 的預設值。

預設情況下,Electron 不允許透過 HTTPS 載入的網站從不安全的來源 (HTTP) 載入和執行腳本、CSS 或外掛程式。將屬性 allowRunningInsecureContent 設定為 true 會停用該保護。

透過 HTTPS 載入網站的初始 HTML 並嘗試透過 HTTP 載入後續資源也稱為「混合內容」。

為什麼?

透過 HTTPS 載入內容可確保載入資源的真實性和完整性,同時加密流量本身。如需更多詳細資訊,請參閱關於 僅顯示安全內容 的章節。

如何運作?

main.js (主要處理程序)
// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
allowRunningInsecureContent: true
}
})
main.js (主要處理程序)
// Good
const mainWindow = new BrowserWindow({})

9. 不要啟用實驗性功能

資訊

此建議是 Electron 的預設值。

Electron 的進階使用者可以使用 experimentalFeatures 屬性來啟用實驗性 Chromium 功能。

為什麼?

實驗性功能顧名思義,是實驗性的,尚未為所有 Chromium 使用者啟用。此外,它們對整個 Electron 的影響可能尚未經過測試。

存在合法的用例,但除非您知道自己在做什麼,否則不應啟用此屬性。

如何運作?

main.js (主要處理程序)
// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
experimentalFeatures: true
}
})
main.js (主要處理程序)
// Good
const mainWindow = new BrowserWindow({})

10. 不要使用 enableBlinkFeatures

資訊

此建議是 Electron 的預設值。

Blink 是 Chromium 背後的渲染引擎名稱。與 experimentalFeatures 一樣,enableBlinkFeatures 屬性允許開發人員啟用預設已停用的功能。

為什麼?

一般來說,如果某項功能預設未啟用,很可能會有充分的理由。存在啟用特定功能的合法用例。作為開發人員,您應該清楚知道為什麼需要啟用某項功能、其後果為何以及它如何影響應用程式的安全性。在任何情況下都不應試探性地啟用功能。

如何運作?

main.js (主要處理程序)
// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
enableBlinkFeatures: 'ExecCommandInJavaScript'
}
})
main.js (主要處理程序)
// Good
const mainWindow = new BrowserWindow()

11. 不要對 WebViews 使用 allowpopups

資訊

此建議是 Electron 的預設值。

如果您使用 <webview>,您可能需要您的 <webview> 標籤中載入的頁面和腳本開啟新視窗。allowpopups 屬性允許它們使用 window.open() 方法建立新的 BrowserWindows。否則,<webview> 標籤不允許建立新視窗。

為什麼?

如果您不需要彈出視窗,最好不要預設允許建立新的 BrowserWindows。這遵循最少必要存取的原則:除非您知道網站需要該功能,否則不要讓網站建立新的彈出視窗。

如何運作?

index.html (渲染器處理程序)
<!-- Bad -->
<webview allowpopups src="page.html"></webview>

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

12. 建立前驗證 WebView 選項

在未啟用 Node.js 整合的渲染器進程中建立的 WebView 將無法自行啟用整合。但是,WebView 將始終使用自己的 webPreferences 建立獨立的渲染器進程。

最好從主進程控制新的 <webview> 標籤的建立,並驗證它們的 webPreferences 沒有停用安全功能。

為什麼?

由於 <webview> 存在於 DOM 中,即使另外停用了 Node.js 整合,它們也可以由在您的網站上執行的腳本建立。

Electron 允許開發人員停用控制渲染器進程的各種安全功能。在大多數情況下,開發人員不需要停用任何這些功能,因此您不應允許為新建立的 <webview> 標籤使用不同的配置。

如何運作?

在附加 <webview> 標籤之前,Electron 將在託管 webContents 上觸發 will-attach-webview 事件。使用此事件來防止建立具有可能不安全選項的 webViews

main.js (主要處理程序)
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()
}
})
})

再次強調,此列表僅能將風險降至最低,但無法完全消除。如果您的目標是顯示網站,使用瀏覽器會是更安全的選擇。

13. 停用或限制導覽

如果您的應用程式不需要導覽,或只需要導覽至已知的頁面,最好將導覽完全限制在已知的範圍內,不允許任何其他類型的導覽。

為什麼?

導覽是常見的攻擊途徑。如果攻擊者可以說服您的應用程式導覽離開目前的頁面,他們可能會強迫您的應用程式開啟網際網路上的網站。即使您的 webContents 設定為更安全(例如停用 nodeIntegration 或啟用 contextIsolation),讓您的應用程式開啟隨機網站會讓攻擊者更容易利用您的應用程式。

常見的攻擊模式是,攻擊者說服您的應用程式使用者以某種方式與應用程式互動,使其導覽至攻擊者的其中一個頁面。這通常透過連結、外掛程式或其他使用者產生的內容來完成。

如何做?

如果您的應用程式不需要導覽,您可以在 will-navigate 處理常式中呼叫 event.preventDefault()。如果您知道您的應用程式可能會導覽至哪些頁面,請在事件處理常式中檢查 URL,並且只有在符合您預期的 URL 時才允許導覽發生。

我們建議您使用 Node 的 URL 解析器。簡單的字串比較有時可能會被欺騙 - 例如,startsWith('https://example.com') 測試會讓 https://example.com.attacker.com 通過。

main.js (主要處理程序)
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. 停用或限制新視窗的建立

如果您有一組已知的視窗,最好限制應用程式中額外視窗的建立。

為什麼?

就像導覽一樣,建立新的 webContents 也是常見的攻擊途徑。攻擊者會嘗試說服您的應用程式建立新的視窗、框架或其他渲染器程序,這些程序的權限比之前更高;或開啟他們之前無法開啟的頁面。

如果您除了已知的需要建立的視窗之外,不需要建立其他視窗,那麼停用視窗建立功能可以為您提供一些額外的安全性,而且無需任何成本。這通常適用於開啟一個 BrowserWindow 且不需要在執行時開啟任意數量額外視窗的應用程式。

如何做?

webContents 會在建立新視窗之前委派給其視窗開啟處理常式。該處理常式將會收到(除了其他參數之外)要求開啟視窗的 url 以及用來建立視窗的選項。我們建議您註冊一個處理常式來監控視窗的建立,並拒絕任何非預期的視窗建立。

main.js (主要處理程序)
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

shell 模組的 openExternal API 允許使用桌面的原生工具開啟指定的協定 URI。例如,在 macOS 上,此函式類似於 open 終端機指令工具,並且將會根據 URI 和檔案類型關聯開啟特定的應用程式。

為什麼?

不當使用 openExternal 可能會被利用來危害使用者的主機。當 openExternal 與不受信任的內容一起使用時,可能會被利用來執行任意指令。

如何做?

main.js (主要處理程序)
//  Bad
const { shell } = require('electron')
shell.openExternal(USER_CONTROLLED_DATA_HERE)
main.js (主要處理程序)
//  Good
const { shell } = require('electron')
shell.openExternal('https://example.com/index.html')

16. 使用最新版本的 Electron

您應該盡力始終使用最新可用的 Electron 版本。每當發布新的主要版本時,您應該盡快嘗試更新您的應用程式。

為什麼?

使用較舊版本的 Electron、Chromium 和 Node.js 建置的應用程式,比使用較新版本這些元件的應用程式更容易成為攻擊目標。一般來說,較舊版本的 Chromium 和 Node.js 的安全性問題和漏洞更容易取得。

Chromium 和 Node.js 都是由數千名才華洋溢的開發人員建置的令人印象深刻的工程壯舉。鑑於它們的普及性,它們的安全性受到同樣熟練的安全性研究人員的仔細測試和分析。許多研究人員會負責任地揭露漏洞,這通常表示研究人員會給予 Chromium 和 Node.js 一些時間來修復問題,然後再發布它們。如果您的應用程式執行的是最新版本的 Electron(因此也是 Chromium 和 Node.js),而且潛在的安全性問題尚未廣為人知,那麼您的應用程式將會更安全。

如何做?

一次遷移一個主要版本的應用程式,同時參考 Electron 的重大變更文件,看看是否需要更新任何程式碼。

17. 驗證所有 IPC 訊息的 sender

您應該始終驗證傳入的 IPC 訊息的 sender 屬性,以確保您不會對不受信任的渲染器執行動作或傳送資訊。

為什麼?

理論上,所有 Web Frame 都可以將 IPC 訊息傳送到主程序,包括某些情況下的 iframe 和子視窗。如果您有 IPC 訊息會透過 event.reply 將使用者資料傳回給傳送者,或者執行渲染器無法原生執行的特權動作,您應該確保您沒有在監聽第三方 Web Frame。

預設情況下,您應該驗證所有 IPC 訊息的 sender

如何做?

main.js (主要處理程序)
// 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:// 協定,並偏好使用自訂協定

您應該從自訂協定而不是 file:// 協定提供本機頁面。

為什麼?

在 Electron 中,file:// 協定比在網頁瀏覽器中獲得更多的權限,即使在瀏覽器中,其處理方式也與 http/https URL 不同。使用自訂協定可讓您更符合經典的網頁 URL 行為,同時保留對何時以及可以載入什麼內容的更多控制。

file:// 上執行的頁面可以單方面存取您機器上的每個檔案,這表示 XSS 問題可用於從使用者機器載入任意檔案。使用自訂協定可以避免此類問題,因為您可以將協定限制為僅提供特定檔案集。

如何做?

請遵循 protocol.handle 範例,以了解如何從自訂協定提供檔案/內容。

19. 檢查您可以變更哪些熔斷器

Electron 提供許多選項,這些選項可能很有用,但很大一部分應用程式可能不需要。為了避免建置您自己的 Electron 版本,可以使用熔斷器來關閉或開啟這些選項。

為什麼?

某些熔斷器(例如 runAsNodenodeCliInspect)允許應用程式在使用特定環境變數或 CLI 引數從命令列執行時有不同的行為。這些可用於透過您的應用程式在裝置上執行指令。

這可以讓外部腳本執行它們可能不被允許執行的指令,但您的應用程式可能擁有執行這些指令的權限。

如何做?

我們製作了一個模組 @electron/fuses,以方便翻轉這些熔斷器。請查看該模組的 README,以取得更多關於使用方法和潛在錯誤案例的詳細資訊,並參閱我們文件中的如何翻轉熔斷器?