nodeIntegrationInSubFrames | Electron 安全

0x01 简介

大家好,今天和大家讨论 nodeIntegrationInSubFrames , 这个选项看起来和 nodeIntegration 很像,不过后面跟了 InSubFrames ,说明是在 SubFrames 中开启  Node.js

这是一个实验性质的选项,决定是否允许在子页面(iframe)或子窗口(child window)中集成Node.js; 预先加载的脚本会被注入到每一个iframe,你可以用 process.isMainFrame 来判断当前是否处于主框架(main frame)中

https://www.electronjs.org/zh/docs/latest/api/structures/browser-window-options


公众号开启了留言功能,欢迎大家留言讨论~

这篇文章也提供了 PDF 版本及 Github ,见文末


  • 0x01 简介

  • 0x02 SubFrames

  • 0x03 测试 iframe

    • 1. 搭建 iframe 服务器

    • 2. 搭建测试环境

    • 3. 测试执行 Node.js

    • 4. 测试预加载脚本

    • 5. 小结

  • 0x04 测试子窗口

  • 0x05 探索可能的子窗口

    • 1. webview 标签

    • 2. WebContentsView

    • 3. object

    • 4. embed

  • 0x06 总结

  • 0x07 PDF 版 & Github

  • 往期文章


0x02 SubFrames

官方文档中 SubFrames 是指 iframe 和子窗口,那 iframe 和子窗口到底是用来干嘛的呢?

其实都是为了在一个页面中嵌入其他页面,例如我想在搜狐的网站中嵌入一段人民日报的新闻页面

这种行为在 Electron 官方文档中叫做 Web 嵌入,关于 web  嵌入,后续我们还会出单独的文章进行讨论

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe

https://www.electronjs.org/zh/docs/latest/tutorial/web-embeds#iframes

iframe 在之前已经参与了很多测试了,在 web 技术中也包含,大家了解得可能已经比较透彻了

通过 iframe 的内容本身有自己独立的上下文(context),而嵌入它的网页被称为父级浏览上下文,当然这是可以嵌套的,就像物理机里装虚拟机,在虚拟机里又装了虚拟机一个道理,而最终的物理机被称为顶级浏览上下文

Electron 之前的测试中,我们只用到了一个窗口,我们一直称之为主窗口,但从逻辑角度来说,没有子窗口的存在,也就没有什么主窗口之说

大家有些时候在使用应用程序的时候,点击某个功能会跳出来一个新的窗口,这个就叫做子窗口

举个例子,我们在电脑版微信中查看公众号文章时,点击文章,会出现一个新的窗口来显示文章内容,而不是在原本的窗口呢,这样原本的窗口可以继续聊天等

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

https://www.electronjs.org/zh/docs/latest/api/browser-window#%E7%88%B6%E5%AD%90%E7%AA%97%E5%8F%A3

创建子窗口的方式也比较简单

const { BrowserWindow } = require('electron')

const top = new BrowserWindow()
const child = new BrowserWindow({ parent: top })
child.show()
top.show()

在配置参数中添加 parent: xxx 指定父窗口即可

问题来了,为什么要设置父子窗口呢?

在之前的一些版本中,似乎子窗口会继承父窗口的一些配置,但后来主要是为了生命周期等,简单来说,我把父窗口关了,子窗口也会被关闭或其他设置

该参数要在父窗口初始化是配置,而不是子窗口

0x03 测试 iframe

1. 搭建 iframe 服务器

192.168.31.216

1.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <div>
    <h1>iframe 页面 - 1.html</h1>
    <script src="iframe_1.js"></script>
  </div>
</body>
</html>

其中 iframe_1.js

require('child_process').exec('deepin-music');

同时,我们再搭建一个 iframe + window.open2.html

<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <div>
    <h1>iframe 2.html</h1>
    <script>window.open("http://192.168.31.216/3.html")</script>
  </div>
</body>
</html>

其中 3.html执行 iframe_2.js,打开相册

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <div>
    <h1>iframe 页面 - 3.html</h1>
    <script src="iframe_2.js"></script>
  </div>
</body>
</html>

iframe_2.js

require('child_process').exec('deepin-album');
nodeIntegrationInSubFrames | Electron 安全

2. 搭建测试环境

关闭 CSP ,关闭 sandbox ,在 index.html 中嵌入 iframe

main.js

// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')
const path = require('node:path')

function createWindow ({
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width800,
    height600,
    webPreferences: {
      sandboxfalse,
      nodeIntegrationInSubFramestrue,
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // and load the index.html of the app.
  mainWindow.loadFile('index.html')

  // Open the DevTools.
  // mainWindow.webContents.openDevTools()
}

app.whenReady().then(() => {
  createWindow()

  app.on('activate'function ({
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed'function ({
  if (process.platform !== 'darwin') app.quit()
})

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> -->
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using Node.js <span id="node-version"></span>,
    Chromium <span id="chrome-version"></span>,
    and Electron <span id="electron-version"></span>.

    <!-- You can also require other files to run in this process -->
    <script src="./renderer.js"></script>
    <iframe src="http://192.168.31.216/1.html"></iframe>
    <iframe src="http://192.168.31.216/2.html"></iframe>
  </body>
</html>

以上安全配置总结为:

  • nodeIntegration: false
  • contextIsolation: true
  • sandbox: false
  • nodeIntegrationInSubFrames: true

3. 测试执行 Node.js

执行测试

nodeIntegrationInSubFrames | Electron 安全

并没有成功执行,我们尝试将安全限制都放开,看看能不能执行

  • nodeIntegration: true
  • contextIsolation: false
  • sandbox: false
  • nodeIntegrationInSubFrames: true
nodeIntegrationInSubFrames | Electron 安全

这次的结果是 iframe 中的 Node.js 成功执行了,但是 iframe + window.open 打开的窗口执行的 Node.js 代码执行失败了

iframe + window.openElectron 14.0 之前版本是可以执行的

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

因此想要在 iframe 中执行 iframe ,需要

  • sandbox: false
  • nodeIntegration: true
  • contextIsolation: false
  • nodeIntegrationInSubFrames: true

缺一不可

这里一定要注意各个安全配置的默认配置,在 2024-04-25左右,我们 NOP TeamElectron 报告了一个安全问题,即虽然官网多个地方强调 sandbox 默认在 Electron 20.0.0 版本开始默认被设置为 true ,官网原话是

从 Electron 20 开始,渲染进程默认启用了沙盒,无需进一步配置。

https://www.electronjs.org/zh/docs/latest/tutorial/sandbox

**但是经过我们的测试,这个配置选项在目前最新版本 Electron 30.0.2 及之前的版本中默认并未设置为 true **

目前我们已经等了 Electron 一周了,还没有在 Github 上给我们反馈,所以这篇文章也会在 Electron 确认并修复漏洞后发布

4. 测试预加载脚本

官网还提到一个功能,就是 Preload 会被注入到每一个 iframe

我们在 Preload 中创建一个 变量/常量,让 iframe 中的脚本 alert 弹窗显示出来

preload.js

window.iframe1 = "iframe_1 has got the value ";
window.iframe_open = "iframe_window_open has got the value";

修改 iframe_1.js

if (window.iframe1 !== undefined) {
    alert(window.iframe1);
else {
    alert("iframe has got nothing");
}

修改 iframe_2.js

if (window.iframe_open !== undefined) {
    alert(window.iframe_open);
else {
    alert("iframe + window.open has got nothing");
}

关闭大部分安全配置

  • sandbox: false
  • nodeIntegration: true
  • contextIsolation: false
  • nodeIntegrationInSubFrames: true

执行测试

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

结果与上面一致,iframe 本身成功获取到了 Preload 中的内容, iframe + window.open 获取失败

iframe + window.openElectron 14.0 之前版本是可以成功获取的

测试一下不同安全配置下,iframe 获取 preload 脚本中的内容的情况

经过测试,发现 nodeIntegrationInSubFramesiframe 获取 preload 中暴露的方法和值只和 nodeIntegrationInSubFrames 本身有关,不受 sandboxnodeIntegrationcontextIsolation 影响,当然,Preload 暴露方法和值的方式受 contextIsolation 影响,当 contextIsolation: true 时需要通过 contextBridge 进行对外暴露

我这边也测试了一下, contextIsolation: true 时,开启 nodeIntegrationInSubFrames 后, iframe 也只是能获取到 contextBridge.exposeInMainWorld 暴露的方法和值,并不能获取到 Preload 中直接通过 window.xxx  这种形式设置的内容

5. 小结

nodeIntegrationInSubFrames 对于 iframe 有两个作用,第一个是赋予 iframe 执行 Node.js 的能力,但是条件比较苛刻,需要同时满足

  • sandbox: false
  • nodeIntegration: true
  • contextIsolation: false
  • nodeIntegrationInSubFrames: true

第二是让 iframe 获取到 Preload 中暴露的方法和值,这个功能只需要设置 nodeIntegrationInSubFrames: true 即可

经过测试,nodeIntegrationInSubFrames 不会让 preload 脚本获得额外执行 Node.js 的能力

所以这个配置项在一些社区在名字问题上争议比较大,默认人员认为这个名字不是很合理

0x04 测试子窗口

这个子窗口是让我比较疑惑的,我看创建子窗口的时候,子窗口可以有自己的安全配置呀,难道没有设置 nodeIntegrationInSubFrames 或设置 nodeIntegrationInSubFrames: false 后,即使子窗口设置了渲染进程可以执行 Node.js 也不会生效吗?

这听起来就很奇怪,我们测试一下就知道了

我们尝试创建子窗口,在主窗口中设置 nodeIntegrationInSubFrames: false ,并在子窗口设置渲染进程可以执行 Node.js ,咱们看看到底能不能执行

main.js

// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')
const path = require('path')

function createPatentWindow ({
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width800,
    height600,
    webPreferences: {
      nodeIntegrationInSubFramesfalse,
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // and load the index.html of the app.
  // mainWindow.loadFile('index.html')
  return mainWindow
}

function createChildWindow (parentWindow{
  const childWindow = new BrowserWindow({
    width800,
    height600,
    webPreferences: {
      sandboxfalse,
      nodeIntegrationtrue,
      contextIsolationfalse,
      nodeIntegrationInSubFramestrue,
      parent: parentWindow
    }
  })
  return childWindow
}

app.whenReady().then(() => {
  const parentWindow = createPatentWindow()
  parentWindow.loadFile('index.html')

  const childWindow = createChildWindow(parentWindow)
  childWindow.loadFile('child.html')
  // createWindow()
  

  app.on('activate'function ({
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed'function ({
  if (process.platform !== 'darwin') app.quit()
})


index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> -->
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using Node.js <span id="node-version"></span>,
    Chromium <span id="chrome-version"></span>,
    and Electron <span id="electron-version"></span>.

    <!-- You can also require other files to run in this process -->
    <script src="./renderer.js"></script>
  </body>
</html>

child.html 这个是子窗口的主页面

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <div>
    <h1>Child Window Page</h1>
    <script>require('child_process').exec('deepin-music')</script>
  </div>
</body>
</html>

执行测试

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

子窗口可以成功执行 Node.js

这样以来, nodeIntegrationInSubFrames 对子窗口 Node.js 的执行就没有影响了呀,而且经过我的测试,在生命周期方面,关闭父窗口,子窗口并不会跟着关闭

测试一下nodeIntegrationInSubFrames: true 时子窗口是否能够读取父窗口的 Preload 中的内容

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

获取失败,看起来官方文档中描述的 child window 并不是官方文档其他部分中的 child window

https://www.electronjs.org/docs/latest/api/browser-window#new-browserwindowoptions

https://www.electronjs.org/docs/latest/api/browser-window#parent-and-child-windows

0x05 探索可能的子窗口

既然子窗口不是指主进程创建的窗口之间的父子关系,那么和 iframe 比较类似的应该就是 <webview>WebContentsView 了,还有 HTML 中的 objectembed

https://www.electronjs.org/zh/docs/latest/tutorial/web-embeds

https://www.electronjs.org/zh/docs/latest/api/webview-tag

https://www.electronjs.org/zh/docs/latest/api/web-contents-view

官方是不建议使用 <webview> 标签来实现嵌入其他页面的,WebContentsViewELectron 30.0.0 新添加的功能,用来替代原本的 BrowserViews

1. webview 标签

对于 webview 标签,在 Electron >= 5.0 版本后,默认不允许,使用的话必须在创建父窗口时显式地设置 webviewTag: true

直接使用上面测试 iframe 执行 Node.js 的服务器即可

经过测试发现, webview 标签加载嵌入的内容是否可以执行 Node.jsnodeIntegrationInSubFrames 并不相关,主要与父窗口安全配置以及 webview 标签本身配置有关系

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

2. WebContentsView

https://www.electronjs.org/zh/docs/latest/api/web-contents-view#class-webcontentsview-extends-view

const { WebContentsView } = require('electron')

const view = new WebContentsView()
view.webContents.loadURL('https://electronjs.org/')

我们尝试在 baseWindow 中添加两个 WebContentsView,看看 WebContentsView 的行为是不是受 baseWindownodeIntegrationInSubFrames 参数的影响

main.js

// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')
const { BaseWindow, WebContentsView } = require('electron')

app.whenReady().then(() => {
  const win = new BaseWindow({ width800height400 , nodeIntegrationtruecontextIsolationfalsesandboxfalsenodeIntegrationInSubFramestrue})

  const view1 = new WebContentsView()
  win.contentView.addChildView(view1)
  view1.webContents.loadURL('http://192.168.31.216/1.html')
  view1.setBounds({ x0y0width400height400nodeIntegrationtruecontextIsolationfalsesandboxfalse})

  const view2 = new WebContentsView()
  win.contentView.addChildView(view2)
  view2.webContents.loadURL('http://192.168.31.216/2.html')
  view2.setBounds({ x400y0width400height400nodeIntegrationtruecontextIsolationfalsesandboxfalse,nodeIntegrationInSubFramestrue })

  app.on('activate'function ({
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed'function ({
  if (process.platform !== 'darwin') app.quit()
})

但比较遗憾的是,没有明确在官网找到更多的信息,尝试了过后也没有发现可以执行 Node.js 的,所以也就没有办法测试 nodeIntegrationInSubFrames 这个参数了

3. object

1) embed 服务器

object 远程加载页面内容

1.html

nodeIntegrationInSubFrames | Electron 安全

2) 测试执行 Node.js

开启 nodeIntegration,关闭上下文隔离进行测试

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

页面正常嵌入了,但是 Node.js 代码没有执行

添加 nodeIntegrationInSubFrames: true

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

成功执行,经过测试,iframe 执行 Node.js 的条件与 iframe 一致

3) 测试预加载脚本

修改 object 服务器内容,获取并控制台输出预加载脚本暴露给渲染进程的值

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

设置 nodeIntegrationInSubFrames: true

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

成功获取到预加载脚本暴露给渲染页面的内容

4) 小结

nodeIntegrationInSubFramesobject 的影响与 iframe 一致

4. embed

1) embed 服务器

embed 远程加载页面内容

1.html

nodeIntegrationInSubFrames | Electron 安全

2) 测试执行 Node.js

开启 nodeIntegration,关闭上下文隔离进行测试

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

页面正常嵌入了,但是 Node.js 代码没有执行

添加 nodeIntegrationInSubFrames: true

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

成功执行,经过测试,embed 执行 Node.js 的条件与 iframe 一致

3) 测试预加载脚本

修改 embed 服务器内容,获取并控制台输出预加载脚本暴露给渲染进程的值

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

设置 nodeIntegrationInSubFrames: true

nodeIntegrationInSubFrames | Electron 安全
nodeIntegrationInSubFrames | Electron 安全

成功获取到预加载脚本暴露给渲染页面的内容

4) 小结

nodeIntegrationInSubFramesembed 的影响与 iframe 一致

0x06 总结

nodeIntegrationInSubFrames 这个配置项的含义随着其他配置项而呈现不同效果,目前来看,影响的对象主要是 iframeobjectembed

  • 如果 nodeIntegrationInSubFrames 设置为 true 时, preload 脚本中暴露的方法和值等将向 iframeobjectembed内暴露,也就是说iframeobjectembed 内部的内容中的 JavaScript 可以直接使用 Preload 脚本中定义好的功能和值

  • 如果嵌入 iframeobjectembed 的宿主页面的安全配置为

    • sandbox: false

    • nodeIntegration: true

    • contextIsolation: false

    • nodeIntegrationInSubFrames: true

其中 sandboxfalse 或默认即可,此时页面中嵌入的 iframeobjectembed 的内容可执行 Node.js

0x07 PDF 版 & Github

PDF

https://pan.baidu.com/s/1PJzvwu4YvBpwiBOsjuu0UA?pwd=14y2


Github

https://github.com/Just-Hack-For-Fun/Electron-Security

往期文章

nodeIntegrationInSubFrames | Electron 安全

有态度,不苟同


原文始发于微信公众号(NOP Team):nodeIntegrationInSubFrames | Electron 安全

版权声明:admin 发表于 2024年5月6日 下午6:07。
转载请注明:nodeIntegrationInSubFrames | Electron 安全 | CTF导航

相关文章