上一篇Electron 安全与你我息息相关文章非常的长,虽然提供了 PDF
版本,但还是导致很多人仅仅是点开看了一下,完读率大概 7.95%
左右,但上一篇真的是我觉得很重要的一篇,对大家了解 Electron
开发的应用程序安全有帮助,与每个人切实相关
但是上一篇文章内容太多,导致很多内容粒度比较粗,可能会给大家造成误解,因此我们打算再写一些文章,一来是将细节补充清楚,二来是再此来呼吁大家注意Electron
安全这件事,如果大家不做出反应,应用程序的开发者是不会有所行动的,这无异于在电脑中埋了一些地雷
我们的公众号已开启了留言功能,大家可以在文章下留言讨论啦~
Electron
的架构以及开发等知识可以参照官网,官网有非常详细的介绍,今天我们要谈论的可能是 Electron
最重要的安全特性之一 —— nodeIntegration
https://www.electronjs.org/zh/docs/latest/ https://www.electronjs.org/zh/docs/latest/tutorial/security#2-%E4%B8%8D%E8%A6%81%E4%B8%BA%E8%BF%9C%E7%A8%8B%E5%86%85%E5%AE%B9%E5%90%AF%E7%94%A8-nodejs-%E9%9B%86%E6%88%90
在 Electron 5
版本以来,这个特性被默认设置为 false
https://releases.electronjs.org/releases/stable?version=5&page=7&limit=2 时间为 2019-04-24 (1815 days ago)
在 Electron 20
版本以来,除非指定 nodeIntegration: true
或 sandbox: false
,不然渲染器会被沙盒化
那这个特性是干什么呢?
官方的解释是:
是否启用
Node integration
官方在安全建议中是这样描述的
加载远程内容时,不论使用的是哪一种渲染器(
BrowserWindow
,BrowserView
或者webview
),最重要的就是绝对不要启用 Node.js 集成。其目的是限制您授予远程内容的权限, 从而使攻击者在您的网站上执行 JavaScript 时更难伤害您的用户。
这个描述似乎在说,开启了 nodeIntegration
之后,渲染进程就可以获取到 NodeJS
的能力,这样渲染进程可以直接使用系统相关的方法,进而达到命令执行的效果
官方眼中的渲染器到底具体是什么呢?预加载脚本?渲染页面中的 JS
?是否还包括那些嵌入的页面中的 JS
,他们都可以获取到这种能力吗?
带着这种疑问,我们开始今天的文章
这篇文章在文末也提供了 PDF
版本
我们根据官方重大更改中提到的两个节点 Electron 5.0
和 Electron 20.0
将整个时间线分成四段,取 5 个点,分别测试一下 nodeIntegration
的具体影响
Electron
官方开发了 Electron Fiddle
程序,可以直接选择 Electron
版本,非常方便,但是需要系统准备对应的 NodeJS
环境,代码就使用默认的,我们在其中 "加料",我们主要从以下几个角度测试
https://www.electronjs.org/zh/fiddle
安全配置 | 测试点 |
---|---|
nodeIntegration | 渲染页面 |
contextIsolation | 预加载脚本 (Preload) |
sanbox | iframe 内页面 |
按照 true
和 false
进行分组
配置序号 | nodeIntegration | contextIsolation | sanbox |
---|---|---|---|
1 | true | true | true |
2 | true | true | false |
3 | true | false | true |
4 | true | false | false |
5 | false | true | true |
6 | false | true | false |
7 | false | false | true |
8 | false | false | false |
每个 Electron
版本对应的 NodeJS
版本如下
Electron 版本 | NodeJS 版本 |
---|---|
Electron 3.0 | NodeJS 10.2.0 |
Electron 5.0 | NodeJS 12.0.0 |
Electron 19.0 | NodeJS 16.14.2 |
Electron 20.0 | NodeJS 16.15.0 |
Electron 29.0 | NodeJS 20.9.0 |
使用 nvm
进行多版本管理,用 Deepin Linux
进行测试
https://github.com/nvm-sh/nvm
低版本的 Nodejs
就用 Ubuntu Desktop 18.04
来进行安装,高版本的 Nodejs
用 Deepin Linux
进行安装测试
安装 Fiddle
https://www.electronjs.org/zh/fiddle
安装 nvm
https://github.com/nvm-sh/nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
Deepin
require('child_process').exec('deepin-music')
Payload
是打开 Deepin
音乐程序
Windows 11
require('child_process').exec('calc')
MacOS
require('child_process').exec('open /System/Applications/Calculator.app')
在内网起一个 http
服务器,设置一个恶意的 html
192.168.31.215
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div>
<h1>Hello World!</h1>
<script>require('child_process').exec('deepin-music')</script>
</div>
</body>
</html>
192.168.31.215
在 iframe
中通过 window.open
打开页面进行执行
2.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div>
<h1>Hello World!</h1>
<script>window.open("http://192.168.31.215/3.html")</script>
</div>
</body>
</html>
3.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div>
<h1>Hello World!</h1>
<script>require('child_process').exec('deepin-music')</script>
</div>
</body>
</html>
为了测试 iframe
,关闭 CSP
安装 NodeJs 10.2.0
nvm install 10.2.0
这里会安装失败,可能是版本太老了,所以该用命令行进行安装
sudo npm install -g electron@3.0.0
在 Fiddle
中添加我们自行安装的 Electron 3.0.0
点击 Run
测试是否可以正常显示
能够正常运行
按照官方手册介绍,Electron 3.0
默认安全配置为
nodeIntegration: true
contextIsolation: false
sandbox: false
成功
成功
iframe
直接执行失败, window.open
执行成功
在默认配置中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 是 |
iframe | 否 |
iframe + window.open | 是 |
nodeIntegration: true
contextIsolation: true
sandbox: true
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
成功
失败
失败
在配置 1 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: true
contextIsolation: true
sandbox: false
这回只需要在上一步中不能执行 NodeJS
的环境中测试
肯定可以
失败
失败
在配置 2 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: true
contextIsolation: false
sandbox: true
这回只需要在配置 1 中不能执行 NodeJS
的环境中测试
肯定可以
失败
失败
在配置 3 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: true
contextIsolation: false
sandbox: false
这回只需要在配置 1 中不能执行 NodeJS
的环境中测试
肯定可以
成功
在 iframe
中通过 window.open
打开页面进行执行
2.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div>
<h1>Hello World!</h1>
<script>window.open("http://192.168.31.215/3.html")</script>
</div>
</body>
</html>
3.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div>
<h1>Hello World!</h1>
<script>require('child_process').exec('deepin-music')</script>
</div>
</body>
</html>
成功执行,当然,我是测试过了,在前面的配置中是无法执行的
在配置 4 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 是 |
iframe | 否 |
iframe + window.open | 是 |
nodeIntegration: false
contextIsolation: true
sandbox: true
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
成功执行
在配置 5 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: false
contextIsolation: false
sandbox: false
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
在配置 9 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
剩下就不用测试了了,可以得出结论了
预加载脚本、渲染页面、iframe
测试点在以下情况可以执行 NodeJS
安全配置 | 预加载脚本 | 渲染页面 | iframe |
---|---|---|---|
nodeIntegration | 不影响 | true | true |
contextIsolation | 不影响 | false | false |
sandbox | 不影响 | false | false |
额外条件 | + window.open |
在默认配置中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 是 |
iframe | 否 |
iframe + window.open | 是 |
Electron 5.0
在 Linux
上无法使用 sandbox: true
,所以 sandbox: true
的部分在 Windows
上进行验证
【Windows 部分配置安装】
安装 nvm-windows
https://github.com/coreybutler/nvm-windows
安装 NodeJS 12.0.0
nvm install 12.0.0
nvm use 12.0.0
新建文件夹并创建 NodeJS
项目
https://www.electronjs.org/zh/docs/latest/tutorial/quick-start
npm init
安装 Electron 5.0
npm install --save-dev electron@5.0.0
package.json
中添加启动参数
准备脚本,也就是 Fiddle
中的 main.js
、renderer.js
、index.html
、preload.js
启动项目测试一下
npm run start
可以正常启动
【Linux部分配置安装】
安装 NodeJS 12.0.0
nvm install 12.0.0
nvm use 12.0.0
安装 Electron 5.0
npm install -g electron@5.0.0
与 Electron 3.0
一样,版本较低,需要手动配置
按照官方手册介绍,Electron 5.0
默认安全配置为
nodeIntegration: false
contextIsolation: true
mixed sandbox: true
sandbox: false
在 5.0
的发布说明中写明了,mixed sandbox
默认启用,但是 mixed sandbox
并不等同于 sandbox
,至于 sandbox 具体是什么,在官网也没有搜索到
https://www.electronjs.org/zh/blog/electron-5-0
成功
失败
失败
在默认配置中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
nodeIntegration: true
contextIsolation: true
sandbox: true
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
成功
失败
失败
在配置 1 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: true
contextIsolation: true
sandbox: false
这回只需要在上一步中不能执行 NodeJS
的环境中测试
肯定可以
失败
失败
在配置 2 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: true
contextIsolation: false
sandbox: true
这回只需要在配置 1 中不能执行 NodeJS
的环境中测试
肯定可以
失败
失败
在配置 3 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
nodeIntegration: true
contextIsolation: false
sandbox: false
这回只需要在配置 1 中不能执行 NodeJS
的环境中测试
肯定可以
成功
在配置 4 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 是 |
iframe | 否 |
iframe + window.open | 是 |
nodeIntegration: false
contextIsolation: true
sandbox: true
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
成功执行
失败
失败
在配置 5 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: false
contextIsolation: false
sandbox: false
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
成功
失败
失败
在配置 9 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
剩下就不用测试了了,可以得出结论了
预加载脚本、渲染页面、iframe
测试点在以下情况可以执行 NodeJS
安全配置 | 预加载脚本 | 渲染页面 | iframe |
---|---|---|---|
nodeIntegration | 不影响 | true | true |
contextIsolation | 不影响 | false | false |
sandbox | 不影响 | false | false |
额外条件 | + window.open |
在默认配置中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
安装 NodeJS 16.14.2
nvm install 16.14.2
安装 Electron 19.0
这回直接用 Fiddle
就可以了
按照官方手册介绍,Electron 19.0
默认安全配置
nodeIntegration: false
contextIsolation: true
sandbox: false
成功
失败
失败
在默认配置中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
nodeIntegration: true
contextIsolation: true
sandbox: true
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
这里可以看出,预加载脚本就只能执行受限制的 NodeJS
方法了,基本无直接危害
失败
失败
![ ](../../../../../Library/Application Support/typora-user-images/image-20240413015409087.png)
失败
在配置 1 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 否 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: true
contextIsolation: true
sandbox: false
这回只需要在上一步中不能执行 NodeJS
的环境中测试,也就是全部环境了
失败
失败
在配置 2 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: true
contextIsolation: false
sandbox: true
这回只需要在配置 1 中不能执行 NodeJS
的环境中测试
失败
失败
直接执行失败, window.open
执行失败
在配置 3 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 否 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
nodeIntegration: true
contextIsolation: false
sandbox: false
这回只需要在配置 1 中不能执行 NodeJS
的环境中测试
肯定可以
成功
失败
在配置 4 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 是 |
iframe | 否 |
iframe + window.open | 否 |
nodeIntegration: false
contextIsolation: false
sandbox: false
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
成功
失败
失败
在配置 9 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
剩下就不用测试了了,可以得出结论了
预加载脚本、渲染页面、iframe
测试点在以下情况可以执行 NodeJS
安全配置 | 预加载脚本 | 渲染页面 | iframe |
---|---|---|---|
nodeIntegration | 不影响 | true | |
contextIsolation | 不影响 | false | |
sandbox | false | false | |
额外条件 |
这里不着急谈绕过和覆盖,那是下一篇文章要探讨的
在默认配置中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
安装 NodeJS 16.15.0
nvm install 16.15.0
安装 Electron 20.0
这回直接用 Fiddle
就可以了
按照官方手册介绍,Electron 20.0
默认安全配置
nodeIntegration: false
contextIsolation: true
sandbox: true
在 Deepin Linux
中,Electron 20
这个版本开发者工具里面的 console
打不开,一点就闪退,可能是 bug
,所以用 MacOS
进行代替
失败
失败
失败
在默认配置中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 否 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
nodeIntegration: true
contextIsolation: true
sandbox: true
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
这里可以看出,预加载脚本就只能执行受限制的 NodeJS
方法了,基本无直接危害
失败
失败
失败
在配置 1 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 否 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: true
contextIsolation: true
sandbox: false
这回只需要在上一步中不能执行 NodeJS
的环境中测试,也就是全部环境了
成功执行
失败
失败
在配置 2 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: true
contextIsolation: false
sandbox: true
这回只需要在配置 1 中不能执行 NodeJS
的环境中测试
失败
失败
直接执行失败, window.open
执行失败
在配置 3 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 否 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
nodeIntegration: true
contextIsolation: false
sandbox: false
这回只需要在配置 1 中不能执行 NodeJS
的环境中测试
肯定可以
成功
失败
在配置 4 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 是 |
iframe | 否 |
iframe + window.open | 否 |
nodeIntegration: false
contextIsolation: false
sandbox: false
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
成功
失败
失败
在配置 9 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: false
contextIsolation: true
sandbox: false
成功
失败
剩下就不用测试了了,可以得出结论了
预加载脚本、渲染页面、iframe
测试点在以下情况可以执行 NodeJS
安全配置 | 预加载脚本 | 渲染页面 | iframe |
---|---|---|---|
nodeIntegration | 不影响 | true | |
contextIsolation | 不影响 | false | |
sandbox | false | false | |
额外条件 |
这里不着急谈绕过和覆盖,那是下一篇文章要探讨的
在默认配置中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 否 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
安装 NodeJS 20.9.0
nvm install 20.9.0
安装 Electron 29.0
这回直接用 Fiddle
就可以了
按照官方手册介绍,Electron 29.0
默认安全配置应该和 20.0
一样
nodeIntegration: false
contextIsolation: true
sandbox: true
失败
失败
失败
在默认配置中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 否 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
nodeIntegration: true
contextIsolation: true
sandbox: true
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
失败
失败
失败
在配置 1 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 否 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: true
contextIsolation: true
sandbox: false
这回只需要在上一步中不能执行 NodeJS
的环境中测试,也就是全部环境了
失败
失败
在配置 2 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: true
contextIsolation: false
sandbox: true
这回只需要在配置 1 中不能执行 NodeJS
的环境中测试
失败
失败
直接执行失败, window.open
执行失败
在配置 3 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 否 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
nodeIntegration: true
contextIsolation: false
sandbox: false
这回只需要在配置 1 中不能执行 NodeJS
的环境中测试
肯定可以
成功
失败
在配置 4 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 是 |
iframe | 否 |
iframe + window.open | 否 |
nodeIntegration: false
contextIsolation: false
sandbox: false
分别尝试在 预加载脚本、渲染进程、iframe
中进行测试
成功
失败
失败
在配置 9 中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 是 |
渲染页面 | 否 |
iframe | 否 |
nodeIntegration: false
contextIsolation: true
sandbox: false
成功
失败,而且因为 bug
还不允许打开 console
失败
剩下就不用测试了了,可以得出结论了
预加载脚本、渲染页面、iframe
测试点在以下情况可以执行 NodeJS
安全配置 | 预加载脚本 | 渲染页面 | iframe |
---|---|---|---|
nodeIntegration | 不影响 | true | |
contextIsolation | 不影响 | false | |
sandbox | false | false | |
额外条件 |
这里不着急谈绕过和覆盖,那是下一篇文章要探讨的
在默认配置中
测试点 | 是/否可以执行 NodeJS |
---|---|
预加载脚本 | 否 |
渲染页面 | 否 |
iframe | 否 |
iframe + window.open | 否 |
将以上几个版本的总结贴到一起,如下
相同的配置在不同版本下表现的结果并不完全相同,在 5.0
和19.0
之间的重大更改中,并未提及 sandbox
对页面影响的变化,但实际上至少在 19.0
版本开始,预加载脚本想要 NodeJS
能力必须设置 sandbox
(这里的 sandbox
是指主进程创建窗口时候的 sandbox
),那到底是从哪个版本开始的呢?
所以我们需要再次测试了,但这里就不把过程中展示出来了
从 Electron 6.0.0
开始 sandbox: true
时, Preload
脚本的 NodeJS
环境为受限环境
再此之前即使设置了 sandbox: true
预加载脚本还是可以加载并使用require('child_process')
这种模块
经过测试 iframe + window.open
的问题在 Electron 14.0.0
中被修复
从上面的对比结果可以看出, nodeIntegration
的作用确实是主要在于渲染进程是否具备执行 NodeJS
的能力,只有当 nodeIntegration
被设置为 true
时,渲染进程(这里不含 preload
) 和 iframe
才有可能获取到执行 NodeJS
的能力,但也同时配合关闭上下文隔离和沙箱
如果采用默认配置,则三个配置项的时间线如下
如果从攻击测试视角查看默认配置项时间线如下
当然,这个是从这篇文章出发的,这篇文章主要讨论 nodeIntegration
这个参数,其他攻击手法及时间线没有放入图中
这里再次呼吁,对于 Electron
程序,我们至少可以按照上一篇文章 Electron安全与你我息息相关 中介绍的普通用户可操作的检查方式对使用的应用程序的安全性进行检查,进而决定使用该程序时的注意事项以及要不要继续使用,当然更好的是向开发者提出建议,采用更安全的开发方式
PDF 版本下载地址
https://pan.baidu.com/s/164fZ7KXzD_jktW_UG3oA2w?pwd=zcj9
我们将 Electron
安全相关的内容建立了一个 Github
项目,地址如下
https://github.com/Just-Hack-For-Fun/Electron-Security