Electron 安全

2023-02-16 17:15 更新

:::報告安全問題

有關如何正確上報 Electron 漏洞的信息,參閱 SECURITY.md.

對于上游Chromium 漏洞: Electron用其他版本的Chromium來更新 欲了解更多信息,請參閱 Electron發(fā)行版 文檔。

:::

前言?

作為網(wǎng)絡開發(fā)人員,我們通常喜歡瀏覽器的強大安全網(wǎng),因為這樣我們編寫的代碼風險較小。 我們的網(wǎng)站在沙盒中被賦予了有限的權力,我們相信我們的用戶享受到的是一個由大型工程師團隊打造的瀏覽器,它能夠快速應對新發(fā)現(xiàn)的安全威脅。

當使用 Electron 時,很重要的一點是要理解 Electron 不是一個 Web 瀏覽器。 它允許您使用熟悉的 Web 技術構建功能豐富的桌面應用程序,但是您的代碼具有更強大的功能。 JavaScript 可以訪問文件系統(tǒng),用戶 shell 等。 這允許您構建更高質量的本機應用程序,但是內在的安全風險會隨著授予您的代碼的額外權力而增加。

考慮到這一點,請注意,展示任意來自不受信任源的內容都將會帶來嚴重的安全風險,而這種風險Electron也沒打算處理。 事實上,最流行的 Electron 應用程序(Atom,Slack,Visual Studio Code 等) 主要顯示本地內容(即使有遠程內容也是無 Node 的、受信任的、安全的內容) - 如果您的應用程序要運行在線的源代碼,那么您需要確保源代碼不是惡意的。

一般準則?

安全是所有人的共同責任

需要牢記的是,你的 Electron 程序安全性除了依賴于整個框架基礎(Chromium、Node.js)、Electron 本身和所有相關 NPM 庫的安全性,還依賴于你自己的代碼安全性。 因此,你有責任遵循下列安全守則:

  • 使用最新版的 Electron 框架搭建你的程序。你最終發(fā)行的產(chǎn)品中會包含 Electron、Chromium 共享庫和 Node.js 的組件。 這些組件存在的安全問題也可能影響你的程序安全性。 你可以通過更新Electron到最新版本來確保像是nodeIntegration繞過攻擊一類的嚴重漏洞已經(jīng)被修復因而不會影響到你的程序。
  • 評估你的依賴項目NPM提供了五百萬可重用的軟件包,而你應當承擔起選擇可信任的第三方庫。 如果你使用了受已知漏洞的過時的庫,或是依賴于維護的很糟糕的代碼,你的程序安全就可能面臨威脅。
  • 遵循安全編碼實踐你的代碼是你的程序安全的第一道防線。 一般的網(wǎng)絡漏洞,例如跨站腳本攻擊(Cross-Site Scripting, XSS),對Electron將造成更大的影響,因此非常建議你遵循安全軟件開發(fā)最佳實踐并進行安全性測試。

隔離不受信任的內容

每當你從不被信任的來源(如一個遠程服務器)獲取代碼并在本地執(zhí)行,其中就存在安全性問題。 例如在默認的 BrowserWindow中顯示一個遠程網(wǎng)站. 如果攻擊者以某種方式設法改變所述內容 (通過直接攻擊源或者通過在應用和實際目的地之間進行攻擊) ,他們將能夠在用戶的機器上執(zhí)行本地代碼。

:::警告

無論如何,在啟用Node.js集成的情況下,你都不該加載并執(zhí)行遠程代碼。 相反,只使用本地文件(和您的應用打包在一起)來執(zhí)行Node.js代碼 如果你想要顯示遠程內容,請使用 <webview> Tag或者 BrowserView,并確保禁用 nodeIntegration 并啟用 contextIsolation

:::

ELECTRON 安全警告

安全警告和建議被打印到開發(fā)者控制臺。 只有當二進制文件的名稱為Electron時,它們才會顯示,這表明開發(fā)人員 當前正在查看控制臺。

你可以通過在process.env 或 window對象上配置ELECTRON_ENABLE_SECURITY_WARNINGS 或ELECTRON_DISABLE_SECURITY_WARNINGS來強制開啟或關閉這些警告。

清單:安全建議?

為加強程序安全性,你至少應當遵循下列規(guī)則:

  1. 只加載安全的內容
  2. 禁止在所有渲染器中使用Node.js集成顯示遠程內容
  3. 在所有渲染器中啟用上下文隔離
  4. 啟用進程沙盒化
  5. 在所有加載遠程內容的會話中使用 ?ses.setPermissionRequestHandler()?.
  6. 不要禁用 ?webSecurity?
  7. 定義一個?Content-Security-Policy?并設置限制規(guī)則(如:?script-src 'self'?)
  8. 不要設置 ?allowRunningInsecureContent? 為 true
  9. 不要開啟實驗性功能
  10. 不要使用?enableBlinkFeatures?
  11. ?<webview>?:不要使用 ?allowpopups?
  12. ?<webview>?:驗證選項與參數(shù)
  13. 禁用或限制網(wǎng)頁跳轉
  14. 禁用或限制新窗口創(chuàng)建
  15. 不要對不可信的內容使用 ?shell.openExternal?
  16. 使用當前版本的 Electron
  17. 驗證所有 IPC 消息的 ?sender?

如果你想要自動檢測錯誤的配置或是不安全的模式,可以使用electronegativity 關于在使用Electron進行應用程序開發(fā)中的潛在薄弱點或者bug,您可以參考開發(fā)者與審核人員指南.

1. 只加載安全的內容

任何不屬于你的應用的資源都應該使用像HTTPS這樣的安全協(xié)議來加載。 換言之, 不要使用不安全的協(xié)議 (如 HTTP)。 同理,我們建議使用WSS,避免使用WS,建議使用FTPS ,避免使用FTP,等等諸如此類的協(xié)議。

為什么?

HTTPS 有兩個主要好處:

  1. 確保數(shù)據(jù)完整性,斷言數(shù)據(jù)在您的應用程序和主機之間傳輸時未被修改。
  2. 它會加密您的用戶和目標主機之間的流量,使竊聽應用與主機之間發(fā)送的信息變得更加困難。

怎么做?

// 不推薦
browserWindow.loadURL ('http://example.com')
// 推薦 
browserWindow.loadURL ('https://example.com')
<!-- 不推薦 -->
<script crossorigin src="http://example.com/react.js" rel="external nofollow" ></script>
<link rel="stylesheet"  rel="external nofollow" target="_blank" >

<!-- 推薦 -->
<script crossorigin src="https://example.com/react.js" rel="external nofollow" ></script>
<link rel="stylesheet"  rel="external nofollow" target="_blank" >

2. 不要為遠程內容啟用 Node.js 集成

INFO

此建議是 Electron 自 5.0.0 以來的默認行為。

加載遠程內容時,不論使用的是哪一種渲染器(BrowserWindow,BrowserView 或者 <webview>),最重要的就是絕對不要啟用 Node.js 集成。 其目的是限制您授予遠程內容的權限, 從而使攻擊者在您的網(wǎng)站上執(zhí)行 JavaScript 時更難傷害您的用戶。

在此之后,你可以為指定的主機授予附加權限。 舉例來說,如果你正在打開一個指向 https://example.com/ 的 BrowserWindow,那么你可以給他剛剛好足夠的權限,但是絕對不要超出這個范圍。

為什么??

如果攻擊者跳過渲染進程并在用戶電腦上執(zhí)行惡意代碼,那么這種跨站腳本(XSS) 攻擊的危害是非常大的。 跨站腳本攻擊很常見,通常情況下,威力僅限于執(zhí)行代碼的網(wǎng)站。 禁用Node.js集成有助于防止XSS攻擊升級為“遠程代碼執(zhí)行” (RCE) 攻擊。

怎么做?

// 不推薦
const mainWindow = new BrowserWindow({
  webPreferences: {
    contextIsolation: false,
    nodeIntegration: true,
    nodeIntegrationInWorker: true
  }
})

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

mainWindow.loadURL('https://example.com')
<!-- 不推薦 -->
<webview nodeIntegration src="page.html"></webview>

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

當禁用Node.js集成時,你依然可以暴露API給你的站點以使用Node.js的模塊功能或特性。 預加載腳本依然可以使用require等Node.js特性, 以使開發(fā)者可以通過?contextBridge API?向遠程加載的內容公開自定義API。

3. 上下文隔離

INFO

此建議是 Electron 自 12.0.0 以來的默認行為。

上下文隔離是Electron的一個特性,它允許開發(fā)者在預加載腳本里運行代碼,里面包含Electron API和專用的JavaScript上下文。 實際上,這意味全局對象如 Array.prototype.push 或 JSON.parse等無法被渲染進程里的運行腳本修改。

Electron使用了和Chromium相同的Content Scripts技術來開啟這個行為。

即便使用了 nodeIntegration: false, 要實現(xiàn)真正的強隔離并且防止使用 Node.js 的功能, contextIsolation 也 必須 開啟.

INFO

獲取關于contextIsolation是什么以及如何使用的更多信息,請參閱我們的 上下文隔離 文檔

4. 啟用進程沙盒化

沙盒 是一項 Chromium 功能,它使用操作系統(tǒng)來顯著地限制渲染器進程可以訪問的內容。 您應該在所有渲染器中啟用沙盒。 不建議在一個未啟動沙盒的進程(包括主進程)中加載、閱讀或處理任何不信任的內容。

INFO

欲了解更多有關進程沙盒的信息,以及如何啟用它,請查看我們專門的 進程沙盒化 文檔。

5. 處理來自遠程內容的會話許可的請求?

當你使用Chrome時,也許見過這種許可請求:每當網(wǎng)站嘗試使用某個特性時,就會彈出讓用戶手動確認(如網(wǎng)站通知)

此API基于Chromium permissions API,并已實現(xiàn)對應的許可類型。

為什么??

默認情況下,Electron將自動批準所有的許可請求,除非開發(fā)者手動配置一個自定義處理函數(shù)。 盡管默認如此,有安全意識的開發(fā)者可能希望默認反著來。

怎么做?

const { session } = require('electron')
const URL = require('url').URL

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

    if (permission === 'notifications') {
      // 批準權限請求
      callback(true)
    }

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

6. 不要禁用 webSecurity

INFO

此建議是 Electron 的默認值。

在渲染進程(BrowserWindow、BrowserView 和 <webview>)中禁用 webSecurity 將導致至關重要的安全性功能被關閉。

不要在生產(chǎn)環(huán)境中禁用webSecurity。

為什么?

禁用 webSecurity 將會禁止同源策略并且將 allowRunningInsecureContent 屬性置 true。 換句話說,這將使得來自其他站點的非安全代碼被執(zhí)行。

怎么做?

// 不推薦
const mainWindow = new BrowserWindow({
  webPreferences: {
    webSecurity: false
  }
})
// 推薦
const mainWindow = new BrowserWindow()
<!-- 不推薦 -->
<webview disablewebsecurity src="page.html"></webview>

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

7. Content Security Policy(內容安全策略)?

內容安全策略(CSP) 是應對跨站腳本攻擊和數(shù)據(jù)注入攻擊的又一層保護措施。 我們建議任何載入到Electron的站點都要開啟。

為什么?

CSP允許Electron通過服務端內容對指定頁面的資源加載進行約束與控制。 如果你定義https://example.com這個源,所屬這個源的腳本都允許被加載,反之https://evil.attacker.com不會被允許加載運行。 對于提升你的應用安全性,設置CSP是個很方便的辦法。

怎么做?

下面的CSP設置使得Electron只能執(zhí)行自身站點和來自apis.example.com的腳本。

// 不推薦
Content-Security-Policy: '*'

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

CSP HTTP headers

Electron 會處理 Content-Security-Policy HTTP 標頭,它可以在 webRequest.onHeadersReceived 中進行設置:

const { session } = require('electron')

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

CSP meta tag

CSP 的首選傳輸機制是一個 HTTP 頭. 但是, 使用 file:// 協(xié)議加載資源時,無法使用此方法。 在某些情況下,使用 <meta> 標記直接在標記(makeup)中對頁面設置策略 很有用:

<meta http-equiv="Content-Security-Policy" content="default-src 'none'">

8. 不要設置 allowRunningInsecureContent 為 true

INFO

此建議是 Electron 的默認值。

默認情況下,Electron不允許網(wǎng)站在HTTPS中加載或執(zhí)行非安全源(HTTP) 中的腳本代碼、CSS或插件。 將allowRunningInsecureContent屬性設為true將禁用這種保護。

當網(wǎng)站的初始內容通過HTTPS加載并嘗試在子請求中加載HTTP的資源時,這被稱為"混合內容"。

為什么?

通過HTTPS加載會將該資源進行加密傳輸,以保證其真實性和完整性。

怎么做?

// 不推薦
const mainWindow = new BrowserWindow({
  webPreferences: {
    allowRunningInsecureContent: true
  }
})
// 推薦
const mainWindow = new BrowserWindow({})

9. 不要開啟實驗性功能

INFO

此建議是 Electron 的默認值。

Electron 的熟練用戶可以通過 experimentalFeatures 屬性來啟用 Chromium 實驗性功能。

為什么??

如名稱所示,實驗性功能是實驗性的,尚未對所有 Chromium 用戶啟用。 此外,它們對整個 Electron 的影響很可能沒有經(jīng)過測試。

盡管存在合理的使用場景,但是除非你知道你自己在干什么,否則你不應該開啟這個屬性。

怎么做?

// 不推薦
const mainWindow = new BrowserWindow({
  webPreferences: {
    experimentalFeatures: true
  }
})
// 推薦
const mainWindow = new BrowserWindow({})

10. 不要使用enableBlinkFeatures

INFO

此建議是 Electron 的默認值。

Blink是Chromium里的渲染引擎名稱。 就像experimentalFeatures一樣,enableBlinkFeatures屬性將使開發(fā)者啟用被默認禁用的特性。

為什么??

通常來說,某個特性默認不被開啟肯定有其合理的原因。 針對特定特性的合理使用場景是存在的。 作為開發(fā)者,你應該非常明白你為何要開啟它,有什么后果,以及對你應用安全性的影響。 在任何情況下都不應該推測性的開啟特性。

怎么做?

// 不推薦
const mainWindow = new BrowserWindow({
  webPreferences: {
    enableBlinkFeatures: 'ExecCommandInJavaScript'
  }
})
// 推薦
const mainWindow = new BrowserWindow()

11. 不要在 WebViews 中使用 allowpopups

INFO

此建議是 Electron 的默認值。

如果您正在使用 <webview> ,您可能需要頁面和腳本加載進您的 <webview> 標簽以打開新窗口。 開啟allowpopups屬性將使得BrowserWindows可以通過window.open()方法創(chuàng)建。 否則, <webview> 標簽內不允許創(chuàng)建新窗口。

為什么??

如果你不需要彈窗,最好使用默認值以關閉新?BrowserWindows?的創(chuàng)建。 以下是最低的權限要求原則:若非必要,不要再網(wǎng)站中創(chuàng)建新窗口。

怎么做?

<!-- 不推薦 -->
<webview allowpopups src="page.html"></webview>

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

12. 創(chuàng)建WebView前確認其選項

通過渲染進程創(chuàng)建的WebView是不開啟Node.js集成的,且也不能由自身開啟。 但是,WebView可以通過其webPreferences屬性創(chuàng)建一個獨立的渲染進程。

通過控制主進程中創(chuàng)建新的<webview>,并確認其webPreferences沒有禁用安全相關特性是個不錯的辦法。

為什么?

由于 <webview> 存在在DOM中,因此即使Node繼承被禁用,它也可以通過運行在您的 網(wǎng)站上的腳本創(chuàng)建它們。

Electron 可以讓開發(fā)者關閉各種控制渲染進程的安全特性。 通常情況下,開發(fā)者并不需要關閉他們中的任何一種 - 因此你不應該允許創(chuàng)建不同配置的<webview>標簽

怎么做?

在 <webview>標簽生效前,Electron將產(chǎn)生一個will-attach-webview事件到webContents中。 利用這個事件來阻止可能含有不安全選項的 webViews 創(chuàng)建。

app.on('web-contents-created', (event, contents) => {
  contents.on('will-attach-webview', (event, webPreferences, params) => {
    // 如果未使用,則刪除預加載腳本或驗證其位置是否合法
    delete webPreferences.preload

    // 禁用 Node.js 集成
    webPreferences.nodeIntegration = false

    // 驗證正在加載的 URL
    if (!params.src.startsWith('https://example.com/')) {
      event.preventDefault()
    }
  })
})

同樣,這個清單只是將風險降低到最低限度,但沒有將其消除。 如果您的目標是展示一個網(wǎng)站,瀏覽器將是一個更安全的選擇。

13. 禁用或限制網(wǎng)頁跳轉?

如果你的應用不需要導航或只需要導航到已知頁面,最好將導航完全限制在該已知范圍內,不允許任何其他類型的導航。

為什么?

導航是一種常見的攻擊媒介。 如果攻擊者可以誘使你的應用導航離開其當前頁面,則他們可能會強制你的應用在 Internet 上打開網(wǎng)站。 即使你的webContents被配置為增強安全(如禁用nodeIntegration或啟用contextIsolation),讓你的應用打開一個任意的網(wǎng)站依舊是非常簡單的操作。

一種常見的攻擊模式是,攻擊者誘導你的應用的用戶與此應用進行能夠使其導航到攻擊者的某個頁面的互動。 這通常是通過鏈接、插件或其他用戶生成的內容完成的。

怎么做?

如果您的應用不需要導航,您可以在 will-navigate 處理器中調用 event.preventDefault()。 如果您知道您的應用程序可能會導航到哪些界面,請在事件處理器中檢查URL,并且僅當它與您預期的URL匹配時才進行導航。

我們建議您使用Node的解析器來處理URL。 簡單的字符串比較有時會出錯 - startsWith('https://example.com')測試會讓https://example.com.attacker.com通過.

const URL = require('url').URL

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. 禁用或限制新窗口創(chuàng)建?

如果您有已知的窗口組,那么限制您的應用程序創(chuàng)建額外的窗口是一個好主意。

為什么?

與導航非常相似,創(chuàng)建新 webContents 是 一種常見的攻擊方式。 攻擊者試圖誘使您的應用創(chuàng)建新的窗口、框架、 或其他渲染過程,擁有比以前更多的權限; 或 打開之前無法打開的頁面。

如果您除了知道需要創(chuàng)建的窗口之外,還不需要創(chuàng)建 窗口,則禁用創(chuàng)建可以免費為您帶來一些額外的 安全性。 對于打開一個 BrowserWindow 并且不需要在運行時打開任意數(shù)量的附加 窗口的應用來說,情況通常如此。

怎么做?

webContents將會在新窗口創(chuàng)建前傳遞給 打開窗口的處理函數(shù)。 除其他參數(shù)外,處理程序 將接收請求打開窗口的 url 以及用于創(chuàng)建窗口的選項。 我們建議您注冊一個處理程序來 監(jiān)視窗口的創(chuàng)建,并拒絕任何意外的窗口創(chuàng)建。

const { shell } = require('electron')

app.on('web-contents-created', (event, contents) => {
  contents.setWindowOpenHandler(({ url }) => {
    // 在此示例中我們要求操作系統(tǒng)
    // 在默認瀏覽器中打開此事件的 url。
    //
    // 關于哪些URL應該被允許通過shell.openExternal打開,
    // 請參照以下項目。
    if (isSafeForExternalOpen(url)) {
      setImmediate(() => {
        shell.openExternal(url)
      })
    }

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

15. 不要對不可信的內容使用 shell.openExternal

Shell 的 openExternalAPI 允許使用桌面的原生工具打開指定的協(xié)議 URI。 例如,在 macOS 上,此功能與 open 終端命令實用程序類似,將基于 URI 和文件類型關聯(lián)打開特定的應用程序。

為什么?

錯誤使用 openExternal 會危害用戶的主機 當 openExternal 使用內容不受信任時,它可以用來執(zhí)行任意命令。

怎么做?

//  不好
const { shell } = require('electron')
shell.openExternal(USER_CONTROLLED_DATA_HERE)
//  好
const { shell } = require('electron')
shell.openExternal('https://example.com/index.html')

16. 使用當前版本的 Electron?

你應該努力始終去使用最新版本的 Electron。 每當發(fā)布新的主要版本時,你應該嘗試盡快更新您的應用。

為什么??

一個使用 Electron、Chromium 和 Node.js 的舊版本構建的應用程序比使用這些組件的最新版本的應用程序更容易成為目標。 一般來說,較舊的 版本的 Chromium 和 Node.js 的安全問題和漏洞利用更多。

Chromium 和 Node.js 都是數(shù)千名有才華的開發(fā)者建造的令人印象深刻的工程。 鑒于他們受歡迎的程度,他們的安全性都經(jīng)過專業(yè)的安全研究人員仔細的測試和分析。 其中許多研究人員負責任地披露漏洞,這通常意味著研究人員會給 Chromium 和 Node.js 一些時間來修復問題,然后再發(fā)布它們。 如果你的應用程序運行的是 Electron 的最新版本 (包括 Chromium 和 Node.js),你的應用程序將更加安全,因為潛在的安全問題并不廣為人知。

怎么做??

一次遷移您的應用一個主要版本, 并且查閱 Electron 的 重大更改 文檔查看是否需要更新代碼。

17. 驗證所有 IPC 消息的 sender

應始終驗證傳入的 IPC 消息的 sender 屬性,確保 未使用不受信任的渲染器執(zhí)行動作或向不受信任的渲染器發(fā)送信息。

為什么?

從理論上講,所有 Web Frame 都可以將 IPC 消息發(fā)送到主進程,包括在某些情況下 iframe 和子窗口。 如果您的 IPC 消息通過 event.reply 向發(fā)件人返回 用戶數(shù)據(jù),或者執(zhí)行了渲染器 無法本機執(zhí)行的特權操作,則應確保您沒有偵聽第三方 web frame。

您應該默認驗證 所有 IPC 消息 sender 。

怎么做?

// 不好
ipcMain.handle('get-secrets', () => {
  return getSecrets();
});

// 好
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;
}


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號