:::報(bào)告安全問題
有關(guān)如何正確上報(bào) Electron 漏洞的信息,參閱 SECURITY.md.
對(duì)于上游Chromium 漏洞: Electron用其他版本的Chromium來更新 欲了解更多信息,請(qǐng)參閱 Electron發(fā)行版 文檔。
:::
作為網(wǎng)絡(luò)開發(fā)人員,我們通常喜歡瀏覽器的強(qiáng)大安全網(wǎng),因?yàn)檫@樣我們編寫的代碼風(fēng)險(xiǎn)較小。 我們的網(wǎng)站在沙盒中被賦予了有限的權(quán)力,我們相信我們的用戶享受到的是一個(gè)由大型工程師團(tuán)隊(duì)打造的瀏覽器,它能夠快速應(yīng)對(duì)新發(fā)現(xiàn)的安全威脅。
當(dāng)使用 Electron 時(shí),很重要的一點(diǎn)是要理解 Electron 不是一個(gè) Web 瀏覽器。 它允許您使用熟悉的 Web 技術(shù)構(gòu)建功能豐富的桌面應(yīng)用程序,但是您的代碼具有更強(qiáng)大的功能。 JavaScript 可以訪問文件系統(tǒng),用戶 shell 等。 這允許您構(gòu)建更高質(zhì)量的本機(jī)應(yīng)用程序,但是內(nèi)在的安全風(fēng)險(xiǎn)會(huì)隨著授予您的代碼的額外權(quán)力而增加。
考慮到這一點(diǎn),請(qǐng)注意,展示任意來自不受信任源的內(nèi)容都將會(huì)帶來嚴(yán)重的安全風(fēng)險(xiǎn),而這種風(fēng)險(xiǎn)Electron也沒打算處理。 事實(shí)上,最流行的 Electron 應(yīng)用程序(Atom,Slack,Visual Studio Code 等) 主要顯示本地內(nèi)容(即使有遠(yuǎn)程內(nèi)容也是無 Node 的、受信任的、安全的內(nèi)容) - 如果您的應(yīng)用程序要運(yùn)行在線的源代碼,那么您需要確保源代碼不是惡意的。
需要牢記的是,你的 Electron 程序安全性除了依賴于整個(gè)框架基礎(chǔ)(Chromium、Node.js)、Electron 本身和所有相關(guān) NPM 庫(kù)的安全性,還依賴于你自己的代碼安全性。 因此,你有責(zé)任遵循下列安全守則:
每當(dāng)你從不被信任的來源(如一個(gè)遠(yuǎn)程服務(wù)器)獲取代碼并在本地執(zhí)行,其中就存在安全性問題。 例如在默認(rèn)的 BrowserWindow
中顯示一個(gè)遠(yuǎn)程網(wǎng)站. 如果攻擊者以某種方式設(shè)法改變所述內(nèi)容 (通過直接攻擊源或者通過在應(yīng)用和實(shí)際目的地之間進(jìn)行攻擊) ,他們將能夠在用戶的機(jī)器上執(zhí)行本地代碼。
:::警告
無論如何,在啟用Node.js集成的情況下,你都不該加載并執(zhí)行遠(yuǎn)程代碼。 相反,只使用本地文件(和您的應(yīng)用打包在一起)來執(zhí)行Node.js代碼 如果你想要顯示遠(yuǎn)程內(nèi)容,請(qǐng)使用 <webview>
Tag或者 BrowserView
,并確保禁用 nodeIntegration
并啟用 contextIsolation
:::
ELECTRON 安全警告
安全警告和建議被打印到開發(fā)者控制臺(tái)。 只有當(dāng)二進(jìn)制文件的名稱為Electron時(shí),它們才會(huì)顯示,這表明開發(fā)人員 當(dāng)前正在查看控制臺(tái)。
你可以通過在
process.env
或window
對(duì)象上配置ELECTRON_ENABLE_SECURITY_WARNINGS
或ELECTRON_DISABLE_SECURITY_WARNINGS
來強(qiáng)制開啟或關(guān)閉這些警告。
為加強(qiáng)程序安全性,你至少應(yīng)當(dāng)遵循下列規(guī)則:
ses.setPermissionRequestHandler()
?.webSecurity
?Content-Security-Policy
?并設(shè)置限制規(guī)則(如:?script-src 'self'
?)allowRunningInsecureContent
? 為 trueenableBlinkFeatures
?<webview>
?:不要使用 ?allowpopups
?<webview>
?:驗(yàn)證選項(xiàng)與參數(shù)shell.openExternal
?sender
?如果你想要自動(dòng)檢測(cè)錯(cuò)誤的配置或是不安全的模式,可以使用electronegativity 關(guān)于在使用Electron進(jìn)行應(yīng)用程序開發(fā)中的潛在薄弱點(diǎn)或者bug,您可以參考開發(fā)者與審核人員指南.
任何不屬于你的應(yīng)用的資源都應(yīng)該使用像HTTPS
這樣的安全協(xié)議來加載。 換言之, 不要使用不安全的協(xié)議 (如 HTTP
)。 同理,我們建議使用WSS
,避免使用WS
,建議使用FTPS
,避免使用FTP
,等等諸如此類的協(xié)議。
HTTPS
有兩個(gè)主要好處:
// 不推薦
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 以來的默認(rèn)行為。
加載遠(yuǎn)程內(nèi)容時(shí),不論使用的是哪一種渲染器(BrowserWindow
,BrowserView
或者 <webview>
),最重要的就是絕對(duì)不要啟用 Node.js 集成。 其目的是限制您授予遠(yuǎn)程內(nèi)容的權(quán)限, 從而使攻擊者在您的網(wǎng)站上執(zhí)行 JavaScript 時(shí)更難傷害您的用戶。
在此之后,你可以為指定的主機(jī)授予附加權(quán)限。 舉例來說,如果你正在打開一個(gè)指向 https://example.com/
的 BrowserWindow,那么你可以給他剛剛好足夠的權(quán)限,但是絕對(duì)不要超出這個(gè)范圍。
如果攻擊者跳過渲染進(jìn)程并在用戶電腦上執(zhí)行惡意代碼,那么這種跨站腳本(XSS) 攻擊的危害是非常大的。 跨站腳本攻擊很常見,通常情況下,威力僅限于執(zhí)行代碼的網(wǎng)站。 禁用Node.js集成有助于防止XSS攻擊升級(jí)為“遠(yuǎn)程代碼執(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>
當(dāng)禁用Node.js集成時(shí),你依然可以暴露API給你的站點(diǎn)以使用Node.js的模塊功能或特性。 預(yù)加載腳本依然可以使用require
等Node.js特性, 以使開發(fā)者可以通過?contextBridge API
?向遠(yuǎn)程加載的內(nèi)容公開自定義API。
INFO
此建議是 Electron 自 12.0.0 以來的默認(rèn)行為。
上下文隔離是Electron的一個(gè)特性,它允許開發(fā)者在預(yù)加載腳本里運(yùn)行代碼,里面包含Electron API和專用的JavaScript上下文。 實(shí)際上,這意味全局對(duì)象如 Array.prototype.push
或 JSON.parse
等無法被渲染進(jìn)程里的運(yùn)行腳本修改。
Electron使用了和Chromium相同的Content Scripts技術(shù)來開啟這個(gè)行為。
即便使用了 nodeIntegration: false
, 要實(shí)現(xiàn)真正的強(qiáng)隔離并且防止使用 Node.js 的功能, contextIsolation
也 必須 開啟.
INFO
獲取關(guān)于
contextIsolation
是什么以及如何使用的更多信息,請(qǐng)參閱我們的 上下文隔離 文檔
沙盒 是一項(xiàng) Chromium 功能,它使用操作系統(tǒng)來顯著地限制渲染器進(jìn)程可以訪問的內(nèi)容。 您應(yīng)該在所有渲染器中啟用沙盒。 不建議在一個(gè)未啟動(dòng)沙盒的進(jìn)程(包括主進(jìn)程)中加載、閱讀或處理任何不信任的內(nèi)容。
INFO
欲了解更多有關(guān)進(jìn)程沙盒的信息,以及如何啟用它,請(qǐng)查看我們專門的 進(jìn)程沙盒化 文檔。
當(dāng)你使用Chrome時(shí),也許見過這種許可請(qǐng)求:每當(dāng)網(wǎng)站嘗試使用某個(gè)特性時(shí),就會(huì)彈出讓用戶手動(dòng)確認(rèn)(如網(wǎng)站通知)
此API基于Chromium permissions API,并已實(shí)現(xiàn)對(duì)應(yīng)的許可類型。
默認(rèn)情況下,Electron將自動(dòng)批準(zhǔn)所有的許可請(qǐng)求,除非開發(fā)者手動(dòng)配置一個(gè)自定義處理函數(shù)。 盡管默認(rèn)如此,有安全意識(shí)的開發(fā)者可能希望默認(rèn)反著來。
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') {
// 批準(zhǔn)權(quán)限請(qǐng)求
callback(true)
}
// 校驗(yàn) URL
if (parsedUrl.protocol !== 'https:' || parsedUrl.host !== 'example.com') {
// Denies the permissions request
return callback(false)
}
})
INFO
此建議是 Electron 的默認(rèn)值。
在渲染進(jìn)程(BrowserWindow
、BrowserView
和 <webview>
)中禁用 webSecurity
將導(dǎo)致至關(guān)重要的安全性功能被關(guān)閉。
不要在生產(chǎn)環(huán)境中禁用webSecurity
。
禁用 webSecurity
將會(huì)禁止同源策略并且將 allowRunningInsecureContent
屬性置 true
。 換句話說,這將使得來自其他站點(diǎn)的非安全代碼被執(zhí)行。
// 不推薦
const mainWindow = new BrowserWindow({
webPreferences: {
webSecurity: false
}
})
// 推薦
const mainWindow = new BrowserWindow()
<!-- 不推薦 -->
<webview disablewebsecurity src="page.html"></webview>
<!-- 推薦 -->
<webview src="page.html"></webview>
內(nèi)容安全策略(CSP) 是應(yīng)對(duì)跨站腳本攻擊和數(shù)據(jù)注入攻擊的又一層保護(hù)措施。 我們建議任何載入到Electron的站點(diǎn)都要開啟。
CSP允許Electron通過服務(wù)端內(nèi)容對(duì)指定頁(yè)面的資源加載進(jìn)行約束與控制。 如果你定義https://example.com
這個(gè)源,所屬這個(gè)源的腳本都允許被加載,反之https://evil.attacker.com
不會(huì)被允許加載運(yùn)行。 對(duì)于提升你的應(yīng)用安全性,設(shè)置CSP是個(gè)很方便的辦法。
下面的CSP設(shè)置使得Electron只能執(zhí)行自身站點(diǎn)和來自apis.example.com
的腳本。
// 不推薦
Content-Security-Policy: '*'
// 推薦
Content-Security-Policy: script-src 'self' https://apis.example.com
Electron 會(huì)處理 Content-Security-Policy
HTTP 標(biāo)頭,它可以在 webRequest.onHeadersReceived
中進(jìn)行設(shè)置:
const { session } = require('electron')
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': ['default-src \'none\'']
}
})
})
CSP 的首選傳輸機(jī)制是一個(gè) HTTP 頭. 但是, 使用 file://
協(xié)議加載資源時(shí),無法使用此方法。 在某些情況下,使用 <meta>
標(biāo)記直接在標(biāo)記(makeup)中對(duì)頁(yè)面設(shè)置策略 很有用:
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
INFO
此建議是 Electron 的默認(rèn)值。
默認(rèn)情況下,Electron不允許網(wǎng)站在HTTPS
中加載或執(zhí)行非安全源(HTTP
) 中的腳本代碼、CSS或插件。 將allowRunningInsecureContent
屬性設(shè)為true
將禁用這種保護(hù)。
當(dāng)網(wǎng)站的初始內(nèi)容通過HTTPS加載并嘗試在子請(qǐng)求中加載HTTP的資源時(shí),這被稱為"混合內(nèi)容"。
通過HTTPS加載會(huì)將該資源進(jìn)行加密傳輸,以保證其真實(shí)性和完整性。
// 不推薦
const mainWindow = new BrowserWindow({
webPreferences: {
allowRunningInsecureContent: true
}
})
// 推薦
const mainWindow = new BrowserWindow({})
INFO
此建議是 Electron 的默認(rèn)值。
Electron 的熟練用戶可以通過 experimentalFeatures
屬性來啟用 Chromium 實(shí)驗(yàn)性功能。
如名稱所示,實(shí)驗(yàn)性功能是實(shí)驗(yàn)性的,尚未對(duì)所有 Chromium 用戶啟用。 此外,它們對(duì)整個(gè) Electron 的影響很可能沒有經(jīng)過測(cè)試。
盡管存在合理的使用場(chǎng)景,但是除非你知道你自己在干什么,否則你不應(yīng)該開啟這個(gè)屬性。
// 不推薦
const mainWindow = new BrowserWindow({
webPreferences: {
experimentalFeatures: true
}
})
// 推薦
const mainWindow = new BrowserWindow({})
INFO
此建議是 Electron 的默認(rèn)值。
Blink是Chromium里的渲染引擎名稱。 就像experimentalFeatures
一樣,enableBlinkFeatures
屬性將使開發(fā)者啟用被默認(rèn)禁用的特性。
通常來說,某個(gè)特性默認(rèn)不被開啟肯定有其合理的原因。 針對(duì)特定特性的合理使用場(chǎng)景是存在的。 作為開發(fā)者,你應(yīng)該非常明白你為何要開啟它,有什么后果,以及對(duì)你應(yīng)用安全性的影響。 在任何情況下都不應(yīng)該推測(cè)性的開啟特性。
// 不推薦
const mainWindow = new BrowserWindow({
webPreferences: {
enableBlinkFeatures: 'ExecCommandInJavaScript'
}
})
// 推薦
const mainWindow = new BrowserWindow()
INFO
此建議是 Electron 的默認(rèn)值。
如果您正在使用 <webview>
,您可能需要頁(yè)面和腳本加載進(jìn)您的 <webview>
標(biāo)簽以打開新窗口。 開啟allowpopups
屬性將使得BrowserWindows
可以通過window.open()
方法創(chuàng)建。 否則, <webview>
標(biāo)簽內(nèi)不允許創(chuàng)建新窗口。
如果你不需要彈窗,最好使用默認(rèn)值以關(guān)閉新?BrowserWindows
?的創(chuàng)建。 以下是最低的權(quán)限要求原則:若非必要,不要再網(wǎng)站中創(chuàng)建新窗口。
<!-- 不推薦 -->
<webview allowpopups src="page.html"></webview>
<!-- 推薦 -->
<webview src="page.html"></webview>
通過渲染進(jìn)程創(chuàng)建的WebView是不開啟Node.js集成的,且也不能由自身開啟。 但是,WebView可以通過其webPreferences
屬性創(chuàng)建一個(gè)獨(dú)立的渲染進(jìn)程。
通過控制主進(jìn)程中創(chuàng)建新的<webview>
,并確認(rèn)其webPreferences沒有禁用安全相關(guān)特性是個(gè)不錯(cuò)的辦法。
由于 <webview>
存在在DOM中,因此即使Node繼承被禁用,它也可以通過運(yùn)行在您的 網(wǎng)站上的腳本創(chuàng)建它們。
Electron 可以讓開發(fā)者關(guān)閉各種控制渲染進(jìn)程的安全特性。 通常情況下,開發(fā)者并不需要關(guān)閉他們中的任何一種 - 因此你不應(yīng)該允許創(chuàng)建不同配置的<webview>
標(biāo)簽
在 <webview>
標(biāo)簽生效前,Electron將產(chǎn)生一個(gè)will-attach-webview
事件到webContents
中。 利用這個(gè)事件來阻止可能含有不安全選項(xiàng)的 webViews
創(chuàng)建。
app.on('web-contents-created', (event, contents) => {
contents.on('will-attach-webview', (event, webPreferences, params) => {
// 如果未使用,則刪除預(yù)加載腳本或驗(yàn)證其位置是否合法
delete webPreferences.preload
// 禁用 Node.js 集成
webPreferences.nodeIntegration = false
// 驗(yàn)證正在加載的 URL
if (!params.src.startsWith('https://example.com/')) {
event.preventDefault()
}
})
})
同樣,這個(gè)清單只是將風(fēng)險(xiǎn)降低到最低限度,但沒有將其消除。 如果您的目標(biāo)是展示一個(gè)網(wǎng)站,瀏覽器將是一個(gè)更安全的選擇。
如果你的應(yīng)用不需要導(dǎo)航或只需要導(dǎo)航到已知頁(yè)面,最好將導(dǎo)航完全限制在該已知范圍內(nèi),不允許任何其他類型的導(dǎo)航。
導(dǎo)航是一種常見的攻擊媒介。 如果攻擊者可以誘使你的應(yīng)用導(dǎo)航離開其當(dāng)前頁(yè)面,則他們可能會(huì)強(qiáng)制你的應(yīng)用在 Internet 上打開網(wǎng)站。 即使你的webContents
被配置為增強(qiáng)安全(如禁用nodeIntegration
或啟用contextIsolation
),讓你的應(yīng)用打開一個(gè)任意的網(wǎng)站依舊是非常簡(jiǎn)單的操作。
一種常見的攻擊模式是,攻擊者誘導(dǎo)你的應(yīng)用的用戶與此應(yīng)用進(jìn)行能夠使其導(dǎo)航到攻擊者的某個(gè)頁(yè)面的互動(dòng)。 這通常是通過鏈接、插件或其他用戶生成的內(nèi)容完成的。
如果您的應(yīng)用不需要導(dǎo)航,您可以在 will-navigate
處理器中調(diào)用 event.preventDefault()
。 如果您知道您的應(yīng)用程序可能會(huì)導(dǎo)航到哪些界面,請(qǐng)?jiān)谑录幚砥髦袡z查URL,并且僅當(dāng)它與您預(yù)期的URL匹配時(shí)才進(jìn)行導(dǎo)航。
我們建議您使用Node的解析器來處理URL。 簡(jiǎn)單的字符串比較有時(shí)會(huì)出錯(cuò) - startsWith('https://example.com')
測(cè)試會(huì)讓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()
}
})
})
如果您有已知的窗口組,那么限制您的應(yīng)用程序創(chuàng)建額外的窗口是一個(gè)好主意。
與導(dǎo)航非常相似,創(chuàng)建新 webContents
是 一種常見的攻擊方式。 攻擊者試圖誘使您的應(yīng)用創(chuàng)建新的窗口、框架、 或其他渲染過程,擁有比以前更多的權(quán)限; 或 打開之前無法打開的頁(yè)面。
如果您除了知道需要?jiǎng)?chuàng)建的窗口之外,還不需要?jiǎng)?chuàng)建 窗口,則禁用創(chuàng)建可以免費(fèi)為您帶來一些額外的 安全性。 對(duì)于打開一個(gè) BrowserWindow
并且不需要在運(yùn)行時(shí)打開任意數(shù)量的附加 窗口的應(yīng)用來說,情況通常如此。
webContents
將會(huì)在新窗口創(chuàng)建前傳遞給 打開窗口的處理函數(shù)。 除其他參數(shù)外,處理程序 將接收請(qǐng)求打開窗口的 url
以及用于創(chuàng)建窗口的選項(xiàng)。 我們建議您注冊(cè)一個(gè)處理程序來 監(jiān)視窗口的創(chuàng)建,并拒絕任何意外的窗口創(chuàng)建。
const { shell } = require('electron')
app.on('web-contents-created', (event, contents) => {
contents.setWindowOpenHandler(({ url }) => {
// 在此示例中我們要求操作系統(tǒng)
// 在默認(rèn)瀏覽器中打開此事件的 url。
//
// 關(guān)于哪些URL應(yīng)該被允許通過shell.openExternal打開,
// 請(qǐng)參照以下項(xiàng)目。
if (isSafeForExternalOpen(url)) {
setImmediate(() => {
shell.openExternal(url)
})
}
return { action: 'deny' }
})
})
Shell 的 openExternal
API 允許使用桌面的原生工具打開指定的協(xié)議 URI。 例如,在 macOS 上,此功能與 open
終端命令實(shí)用程序類似,將基于 URI 和文件類型關(guān)聯(lián)打開特定的應(yīng)用程序。
錯(cuò)誤使用 openExternal
會(huì)危害用戶的主機(jī) 當(dāng) openExternal 使用內(nèi)容不受信任時(shí),它可以用來執(zhí)行任意命令。
// 不好
const { shell } = require('electron')
shell.openExternal(USER_CONTROLLED_DATA_HERE)
// 好
const { shell } = require('electron')
shell.openExternal('https://example.com/index.html')
你應(yīng)該努力始終去使用最新版本的 Electron。 每當(dāng)發(fā)布新的主要版本時(shí),你應(yīng)該嘗試盡快更新您的應(yīng)用。
一個(gè)使用 Electron、Chromium 和 Node.js 的舊版本構(gòu)建的應(yīng)用程序比使用這些組件的最新版本的應(yīng)用程序更容易成為目標(biāo)。 一般來說,較舊的 版本的 Chromium 和 Node.js 的安全問題和漏洞利用更多。
Chromium 和 Node.js 都是數(shù)千名有才華的開發(fā)者建造的令人印象深刻的工程。 鑒于他們受歡迎的程度,他們的安全性都經(jīng)過專業(yè)的安全研究人員仔細(xì)的測(cè)試和分析。 其中許多研究人員負(fù)責(zé)任地披露漏洞,這通常意味著研究人員會(huì)給 Chromium 和 Node.js 一些時(shí)間來修復(fù)問題,然后再發(fā)布它們。 如果你的應(yīng)用程序運(yùn)行的是 Electron 的最新版本 (包括 Chromium 和 Node.js),你的應(yīng)用程序?qū)⒏影踩驗(yàn)闈撛诘陌踩珕栴}并不廣為人知。
一次遷移您的應(yīng)用一個(gè)主要版本, 并且查閱 Electron 的 重大更改 文檔查看是否需要更新代碼。
應(yīng)始終驗(yàn)證傳入的 IPC 消息的 sender
屬性,確保 未使用不受信任的渲染器執(zhí)行動(dòng)作或向不受信任的渲染器發(fā)送信息。
從理論上講,所有 Web Frame 都可以將 IPC 消息發(fā)送到主進(jìn)程,包括在某些情況下 iframe 和子窗口。 如果您的 IPC 消息通過 event.reply
向發(fā)件人返回 用戶數(shù)據(jù),或者執(zhí)行了渲染器 無法本機(jī)執(zhí)行的特權(quán)操作,則應(yīng)確保您沒有偵聽第三方 web frame。
您應(yīng)該默認(rèn)驗(yàn)證 所有 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;
}
更多建議: