:::報告安全問題
有關如何正確上報 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 庫的安全性,還依賴于你自己的代碼安全性。 因此,你有責任遵循下列安全守則:
每當你從不被信任的來源(如一個遠程服務器)獲取代碼并在本地執(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ī)則:
ses.setPermissionRequestHandler()
?.webSecurity
?Content-Security-Policy
?并設置限制規(guī)則(如:?script-src 'self'
?)allowRunningInsecureContent
? 為 trueenableBlinkFeatures
?<webview>
?:不要使用 ?allowpopups
?<webview>
?:驗證選項與參數(shù)shell.openExternal
?sender
?如果你想要自動檢測錯誤的配置或是不安全的模式,可以使用electronegativity 關于在使用Electron進行應用程序開發(fā)中的潛在薄弱點或者bug,您可以參考開發(fā)者與審核人員指南.
任何不屬于你的應用的資源都應該使用像HTTPS
這樣的安全協(xié)議來加載。 換言之, 不要使用不安全的協(xié)議 (如 HTTP
)。 同理,我們建議使用WSS
,避免使用WS
,建議使用FTPS
,避免使用FTP
,等等諸如此類的協(xié)議。
HTTPS
有兩個主要好處:
// 不推薦
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" >
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。
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
是什么以及如何使用的更多信息,請參閱我們的 上下文隔離 文檔
沙盒 是一項 Chromium 功能,它使用操作系統(tǒng)來顯著地限制渲染器進程可以訪問的內容。 您應該在所有渲染器中啟用沙盒。 不建議在一個未啟動沙盒的進程(包括主進程)中加載、閱讀或處理任何不信任的內容。
INFO
欲了解更多有關進程沙盒的信息,以及如何啟用它,請查看我們專門的 進程沙盒化 文檔。
當你使用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)
}
})
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>
內容安全策略(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
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 的首選傳輸機制是一個 HTTP 頭. 但是, 使用 file://
協(xié)議加載資源時,無法使用此方法。 在某些情況下,使用 <meta>
標記直接在標記(makeup)中對頁面設置策略 很有用:
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
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({})
INFO
此建議是 Electron 的默認值。
Electron 的熟練用戶可以通過 experimentalFeatures
屬性來啟用 Chromium 實驗性功能。
如名稱所示,實驗性功能是實驗性的,尚未對所有 Chromium 用戶啟用。 此外,它們對整個 Electron 的影響很可能沒有經(jīng)過測試。
盡管存在合理的使用場景,但是除非你知道你自己在干什么,否則你不應該開啟這個屬性。
// 不推薦
const mainWindow = new BrowserWindow({
webPreferences: {
experimentalFeatures: true
}
})
// 推薦
const mainWindow = new BrowserWindow({})
INFO
此建議是 Electron 的默認值。
Blink是Chromium里的渲染引擎名稱。 就像experimentalFeatures
一樣,enableBlinkFeatures
屬性將使開發(fā)者啟用被默認禁用的特性。
通常來說,某個特性默認不被開啟肯定有其合理的原因。 針對特定特性的合理使用場景是存在的。 作為開發(fā)者,你應該非常明白你為何要開啟它,有什么后果,以及對你應用安全性的影響。 在任何情況下都不應該推測性的開啟特性。
// 不推薦
const mainWindow = new BrowserWindow({
webPreferences: {
enableBlinkFeatures: 'ExecCommandInJavaScript'
}
})
// 推薦
const mainWindow = new BrowserWindow()
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>
通過渲染進程創(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)站,瀏覽器將是一個更安全的選擇。
如果你的應用不需要導航或只需要導航到已知頁面,最好將導航完全限制在該已知范圍內,不允許任何其他類型的導航。
導航是一種常見的攻擊媒介。 如果攻擊者可以誘使你的應用導航離開其當前頁面,則他們可能會強制你的應用在 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()
}
})
})
如果您有已知的窗口組,那么限制您的應用程序創(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' }
})
})
Shell 的 openExternal
API 允許使用桌面的原生工具打開指定的協(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')
你應該努力始終去使用最新版本的 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 的 重大更改 文檔查看是否需要更新代碼。
應始終驗證傳入的 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;
}
更多建議: