Electron使用preload预加载及安全策略
使用 Electron 很重要的一点是要理解 Electron 不是一个 Web 浏览器。 它允许您使用熟悉的 Web 技术构建功能丰富的桌面应用程序,但是您的代码具有更强大的功能。 JavaScript 可以访问文件系统,用户 shell 等。 这允许您构建更高质量的本机应用程序,但是内在的安全风险会随着授予您的代码的额外权力而增加。
事实上,最流行的 Electron 应用程序(Atom,Slack,Visual Studio Code 等) 主要显示本地内容(即使有远程内容也是无 Node 的、受信任的、安全的内容) - 如果您的应用程序要运行在线的源代码,那么您需要确保源代码不是恶意的。
一、preload
preload是electron的预加载机制,可以理解为在electron创建时将nodejs环境加载到渲染进程中使用。程序的进程是相互独立的,electron中渲染进程和主进程的协同工作一般采用IPC进程通信或者在渲染进程中集成node环境,还有早期比较低效的remote模块方式使用node环境。除非保证渲染进程的JavaScript都是可信安全的,否则不推荐在渲染进程集成node环境。使用preload的目的是在electron中不开启node环境集成情况下使用node的模块,避免不安全的JavaScript代码随意使用node环境。
preload的工作是在创建窗体时预加载需要node模块到渲染进程,然后以API方式暴露给渲染进程调用,preload共享渲染进程的window、document对象,因此preload可以轻松操作DOM,而渲染进程不共享preload的global对象。
preload.js
const { contextBridge, ipcRenderer} = require('electron');
const fs = require('fs'); contextBridge.exposeInMainWorld('fsApi', {writeFile: (filename, text, callback) => {fs.writeFile(filename, text, callback);},readFile: (filename, encode, callback) => {fs.readFile(filename, encode, callback);},unlink: (filename, callback) => {fs.unlink(filename, callback);}
});contextBridge.exposeInMainWorld('ipcRendererApi', {send: (channel, args) => ipcRenderer.send(channel, args),once: (channel, listener) => ipcRenderer.once(channel, listener),on: (channel, listener) => ipcRenderer.on(channel, listener),
});
调用预加载API
ipcRendererApi.send('close', args)
二、安全策略
(一)webPreferences属性
Electron安全策略几乎都在webPreferences属性中设置,通过属性来开启或者关闭渲染进程相关安全选项。
属性 | 值 | 默认 | 作用 | 说明 |
---|---|---|---|---|
nodeIntegration 或 nodeIntegrationInWorker |
true/false | false | 渲染进程集成node环境 |
默认关闭,当渲染进程加载远程内容时必须关闭,远程内容不确定性,避免跳过渲染进程在node环境下执行JS脚本。webview中同样不推荐使用nodeIntegration属性。<webview nodeIntegration src="page.html"></webview>
|
preload | 预加载脚本 |
当禁用Node.js集成时,你依然可以暴露API给你的站点以使用Node.js的模块功能或特性。即预加载node模块到渲染进程使用,另外预加载的模块可以直接访问渲染进程的DOM。 electron早期继承remote模块,用于在渲染进程调用主进程的功能,当前最新的electron已经不是默认模块需要另外安装,使用remote模块相比较于IPC通信更低效,建议使用IPC与主进程通信。 |
||
contextIsolation | true/false | true | 开启JS上下文隔离 | 默认开启,即便使用了 nodeIntegration: false, 要实现真正的强隔离并且防止使用 Node.js 的功能, contextIsolation 也 必须 开启。一般情况下结合nodeIntegration一起使用。 |
webSecurity | true/false | true | 安全性功能 |
在渲染进程(BrowserWindow、BrowserView 和 <webview> )禁用webSecurity,这将使得来自其他站点的非安全代码被执行。
|
allowRunningInsecureContent | true/false | false | 允许运行不安全内容 | 默认情况下,Electron不允许网站在HTTPS中加载或执行非安全源(HTTP) 中的脚本代码、CSS或插件。 将allowRunningInsecureContent属性设为true将禁用这种保护。 |
experimentalFeatures | true/false | false | 实验性功能 | 实验性功能是实验性的,尚未对所有 Chromium 用户启用。 |
enableBlinkFeatures | Blink渲染引擎特性 | 通常来说,某个特性默认不被开启肯定有其合理的原因。 针对特定特性的合理使用场景是存在的。 作为开发者,你应该非常明白你为何要开启它,有什么后果,以及对你应用安全性的影响。 在任何情况下都不应该推测性的开启特性。 |
(二)加载安全内容
在渲染进程中只加载HTTPS的安全内容
main.js (Main Process)
// 不推荐
browserWindow.loadURL ('http://example.com')
// 推荐
browserWindow.loadURL ('https://example.com')
index.html (Renderer Process)
<!-- 不推荐 -->
<script crossorigin src="http://example.com/react.js"></script>
<link rel="stylesheet" href="http://example.com/style.css"><!-- 推荐 -->
<script crossorigin src="https://example.com/react.js"></script>
<link rel="stylesheet" href="https://example.com/style.css">
(三)启用进程沙盒化
您应该在所有渲染器中启用沙盒。 不建议在一个未启动沙盒的进程(包括主进程)中加载、阅读或处理任何不信任的内容。Electron20及以后的版本默认开启沙盒,这导致preload加载nodejs的插件报错,在确保当前程序的代码安全性的前提下可以手动关闭沙盒。详情
(四)限制请求会话权限
通过session来过滤请求协议或者域名,electron该API基于Chromium permissions API实现。
main.js (Main Process)
const { session } = require('electron')
const URL = require('url').URLsession.fromPartition('some-partition').setPermissionRequestHandler((webContents, permission, callback) => {const parsedUrl = new URL(webContents.getURL())if (permission === 'notifications') {// Approves the permissions requestcallback(true)}// Verify URLif (parsedUrl.protocol !== 'https:' || parsedUrl.host !== 'example.com') {// Denies the permissions requestreturn callback(false)}})
(五)Content-Security-Policy(CSP)
CSP是HTTP协议标头,限制允许渲染进程执行的脚本,是应对跨站脚本攻击和数据注入攻击的策略。
// 不推荐
Content-Security-Policy: '*'// 推荐
Content-Security-Policy: script-src 'self' https://apis.example.com
main.js (Main Process)
const { session } = require('electron')session.defaultSession.webRequest.onHeadersReceived((details, callback) => {callback({responseHeaders: {...details.responseHeaders,'Content-Security-Policy': ['default-src \'none\'']}})
})
index.html (Renderer Process)
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
仅适用于HTTP协议
(六)Webview的allowpopups属性
index.html (Renderer Process)
<!-- 不推荐 -->
<webview allowpopups src="page.html"></webview><!-- 推荐 -->
<webview src="page.html"></webview>
(七)Webview标签选项
渲染进程通过标签创建的webview没有node继承,而从主进程创建的webview可以加成node。在 标签生效前,Electron将产生一个will-attach-webview事件到webContents中。 利用这个事件来阻止可能含有不安全选项的 webViews 创建。
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 legitimatedelete webPreferences.preload// Disable Node.js integrationwebPreferences.nodeIntegration = false// Verify URL being loadedif (!params.src.startsWith('https://example.com/')) {event.preventDefault()}})
})
(八)禁用或限制网页跳转
该功能主要是防止跳转到指定页面后执行JS,从而存在跨站点攻击隐患,即便是关闭node集成开启上下文隔离的情况下,也不应该允许任意网页跳转。
如果您的应用不需要导航,您可以在 will-navigate 处理器中调用 event.preventDefault()。 如果您知道您的应用程序可能会导航到哪些界面,请在事件处理器中检查URL,并且仅当它与您预期的URL匹配时才进行导航。
main.js (Main Process)
const URL = require('url').URLapp.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()}})
})
(九)禁止或限制新窗口创建
当打开新窗口时,注册事件来处理即将打开新窗口的URL,从而过滤掉不安全的url打开新窗口。
main.js (Main Process)
const { shell } = require('electron')app.on('web-contents-created', (event, contents) => {contents.setWindowOpenHandler(({ url }) => {// 在此示例中我们要求操作系统// 在默认浏览器中打开此事件的 url。//// 关于哪些URL应该被允许通过shell.openExternal打开,// 请参照以下项目。if (isSafeForExternalOpen(url)) {setImmediate(() => {shell.openExternal(url)})}return { action: 'deny' }})
})
(十)shell.openExternal
shell.openExternal可以用来执行任意命令,不受信任的内容不要使用该API。
main.js (Main Process)
// 不好
const { shell } = require('electron')
shell.openExternal(USER_CONTROLLED_DATA_HERE)
// 好
const { shell } = require('electron')
shell.openExternal('https://example.com/index.html')
(十一)验证IPC消息发送者
任何窗口都可以向主进程发送消息,主进程回复渲染进程时,应该确定发送者身份。
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 allowlistif ((new URL(frame.url)).host === 'electronjs.org') return true;return false;
}
Electron使用preload预加载及安全策略相关推荐
- 不加载执行js_前端性能优化:preload 预加载页面资源
网上看到一篇来自蚂蚁金服数据体验团队的文章,觉得不错,分享给大伙:https://juejin.im/post/5a7fb09bf265da4e8e785c38 本文主要介绍preload的使用,以及 ...
- 前端性能优化:dns-prefetch和preload预加载资源
前端页面加载的时候我们有时会用到一些非模块化的库都是用cdn引入的,如果体积很大的话会阻塞页面的加载,并且这个库可能只在特定页面调用,这样体验很不好 这里可以用dns-prefetch按需加载解决. ...
- uniapp开发h5页面实现图片预加载功能
背景 h5页面,很多时候存在大量的图片.动画,这些都需要下载大量的静态资源,如果我们直接打开页面,会发现部分图片正在加载或者还未下载的现象,严重影响体验效果. 为了解决这个问题,我们需要进行图片预加载 ...
- 【第1159期】CSS预加载Preload
前言 看天气预报,今天好多地方都开始下雪了.今日早读文章由@李斌分享. 正文从这开始- Preload 作为一个新的web标准,旨在提高性能和为web开发人员提供更细粒度的加载控制.Preload使开 ...
- 通过rel=preload进行内容预加载
proload基础 <link> 元素的 rel 属性的属性值preload能够让你在你的HTML页面中 <head>元素内部书写一些声明式的资源获取请求,可以指明哪些资源是在 ...
- 图片预加载插件 preLoad.js
1.preLoad.js插件 1 /*! 2 * preLoad.js v1.0 3 * (c) 2017 Meng Fangui 4 * Released under the MIT License ...
- GORM v2 关联预加载Preload和Joins的区别
前言 本文中使用到的数据表结构以及GORM版本的区分详见以下文章:GORM v2 一对一关联查询使用(Belongs To .Has One) 执行区别 调用gorm的Debug方法打印一下一对一关联 ...
- 纯web项目不能使用mui.preload进行页面预加载的解决办法
首先: 纯web项目不能使用mui.preload进行页面预加载的, 比如[基于微信的web项目](http://ask.dcloud.net.cn/question/20644) 怎么办呢? 自己写 ...
- 预加载属性 preload 与 prefetch 区别
原文链接:https://waynegong.cn/posts/40528.html TLDR: preload 告诉浏览器立即加载资源; prefetch 告诉浏览器在空闲时才开始加载资源: pre ...
最新文章
- 3指南针旋转_Qt编写自定义控件6-指南针仪表盘
- java collections读书笔记(4) stack
- SAP Spartacus OccEndpointsService单元测试之getBaseEndpoint
- sfs2x 连接 mongodb
- 弱鸡儿长乐爆肝旅Day8
- 已潜伏17年!严重的“可蠕虫”系统漏洞影响所有Windows Server
- 【ArcGIS|空间分析|网络分析】7 使用一支车队服务一组停靠点
- 嵌入式系统开发笔记94:使用FlyMcu连接STM32开发板
- unityShader
- 测试用例(等价类划分法)
- AutoJs学习-微信群发和引流
- 国内第一款企业集中管理平台--极通EWEBS3.0
- 单片机是什么?51单片机和stm32有什么区别?
- 单端正激(Forward)变换器的工作原理CCM模式下电路设计参数计算
- 关于解决The Operation Couldn't be Completed
- 大数据的分析技术,主要有哪些?
- 设计模式七大原则——依赖倒转原则
- 支付宝17年新春红包技术体系剖析
- 树莓派WIFI配置遇到的坑 之 连接不上WIFI
- css文字超出宽度自动换行