首页 / Electron 入门教程 / 常见安全漏洞与防范

Electron 入门教程

常见安全漏洞与防范

本教程共 45 篇 · 第 40 篇 · 更新于 2026-08-03

Electron安全漏洞XSSRCE供应链命令注入

40. 常见安全漏洞与防范

本节目标

  • 弄清 XSS 如何在 Electron 里升级为 RCE。
  • 认识原型链污染的危险与写法。
  • 避免命令注入与危险的 shell.openExternal
  • 理解供应链攻击与本地文件暴露风险。
  • 掌握针对每类漏洞的具体防范措施。

1-1 XSS 升级为 RCE

跨站脚本(XSS)在普通网页里通常只能搞乱当前页面,危害有限。但在 Electron 里,如果渲染进程启用了 Node 集成,攻击者注入的脚本就能跳出渲染进程,直接在用户电脑上执行任意代码,这就是「远程代码执行」(RCE)。XSS 在 Electron 里的破坏力被 Node 能力放大了。

攻击路径通常是这样的:你的应用加载了一个包含不可信内容的页面,页面存在 XSS;攻击者注入脚本后调用 require('child_process').exec 或读写文件系统,从而完全控制用户机器。

防范的核心就是上一章讲的三件套:关闭 nodeIntegration、开启 contextIsolation、启用 sandbox。只要渲染进程拿不到 Node 原语,XSS 就只能困在页面内部。另外务必设置严格的 Content-Security-Policy,从源头减少脚本注入。

<!-- 渲染进程 index.html:用 meta 设置 CSP,禁止外部脚本 -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
// 主进程:用响应头进一步收紧 CSP
const { session } = require('electron')

session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
  callback({
    responseHeaders: {
      ...details.responseHeaders,
      'Content-Security-Policy': ["default-src 'none'"]
    }
  })
})

1-2 原型链污染

JavaScript 里所有对象都继承自 Object.prototype。如果你用用户输入作为键,去递归合并对象,就可能意外地改写原型上的属性,比如给所有对象都挂上危险的默认值。这类「原型链污染」在 Electron 里尤其危险,因为被污染的对象可能流入主进程逻辑,改变行为甚至绕过校验。

典型的危险写法是用 obj[a][b] = value 这种路径赋值,而 ab 来自外部。防范方式是:合并对象前过滤掉 __proto__constructorprototype 这类键,或改用不会触碰原型的工具函数(例如 Object.create(null) 创建无原型对象,或 lodash 的 merge 配合 cloneSafe 思路)。

// ❌ 不要这样做:用户输入直接作为嵌套键,可能污染原型
function badMerge (target, source) {
  for (const key in source) {
    if (typeof source[key] === 'object') {
      target[key] = badMerge(target[key] || {}, source[key])
    } else {
      target[key] = source[key] // key 若为 '__proto__' 即污染
    }
  }
  return target
}
// ✅ 安全:跳过原型相关键,且目标用无原型对象
function safeMerge (target, source) {
  for (const key of Object.keys(source)) {
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue
    if (source[key] && typeof source[key] === 'object') {
      target[key] = safeMerge(target[key] || Object.create(null), source[key])
    } else {
      target[key] = source[key]
    }
  }
  return target
}

1-3 命令注入

当你的应用需要调用外部程序时,最容易犯的错误是把用户输入直接拼进 shell 命令。攻击者可以注入额外的命令或参数,实现越权执行。Electron 里的 child_process.exec 会启动一个 shell,拼字符串尤其危险。

防范原则有两条。第一,优先用 child_process.execFilespawn,它不通过 shell,参数以数组形式传入,不会被当作命令解析。第二,永远不要拼接不可信字符串;如果必须拼,也要做严格的白名单校验。

// ❌ 不要这样做:用户输入被当作 shell 命令的一部分
const { exec } = require('child_process')

exec('openFile ' + userInput, (err, out) => { /* ... */ })
// ✅ 安全:用 execFile,参数以数组传入,绕过 shell 解析
const { execFile } = require('child_process')

const allowed = ['report.pdf', 'data.csv']
if (!allowed.includes(userInput)) return
execFile('openFile', [userInput], (err, out) => { /* ... */ })

另外,shell.openExternal 也常被误用:把用户可控的 URL 直接交给它,可能被用来执行任意命令(例如在 macOS 上通过特定协议触发)。只把它用于你能完全控制或经过严格白名单的地址。

// ❌ 不要这样做:传入用户控制的链接
shell.openExternal(userControlledUrl)
// ✅ 安全:仅打开可信的硬编码地址
shell.openExternal('https://example.com/index.html')

1-4 供应链攻击

你的应用最终会打包一份由 Electron、Chromium 共享库、Node.js 组成的运行时,再加上几十上百个 npm 依赖。任何一个过时或被投毒的库,都会拖垮整包应用的安全。npm 上有几十万个包,挑选可信第三方库是你的责任。

防范措施包括:只用维护良好、下载量高的库;定期跑 npm audit 查看已知漏洞;把依赖版本锁进 package-lock.json;对关键能力(如自动更新、加密)优先选用官方或社区公认的成熟方案。升级 Electron 到新版本同样重要,因为大量漏洞(包括 nodeIntegration 绕过类)只在旧版本中存在,新版早已修复。

具体到命令层面,定期执行 npm audit 能列出依赖树里已知漏洞以及对应的修复建议;把依赖锁进 package-lock.json 则保证每次安装都拿到完全一致的版本,避免某次 npm install 悄悄拉入有问题的新版。对自动更新、加密、压缩这类关键能力,优先选用下载量大、维护活跃的成熟库,而不是随手安装一个来路不明的包。依赖越多,攻击面越大,定期做「依赖瘦身」同样重要。还要特别关注传递依赖:你的直接依赖所依赖的包,往往才是漏洞真正的重灾区,审计时不要只盯着第一层。

1-5 本地文件暴露

使用 file:// 协议加载本地页面时,Electron 会比浏览器给页面更多权限。运行在 file:// 下的页面能单向访问机器上的任意文件,这意味着一旦存在 XSS,攻击者就可以读取用户机器上的文件。直接用 file:// 还会让 CSP 这类基于 HTTP 头的防护失效。

更好的做法是:用自定义协议来提供本地页面,而不是 file://。通过 protocol.handle 注册一个私有协议(例如 app://),你就能精确控制哪些文件可以被加载、何时可以加载,把访问范围限制在你打包的资源内。

// 主进程:注册自定义协议来替代 file://
const { app, protocol } = require('electron')
const path = require('node:path')
const fs = require('node:fs')

app.whenReady().then(() => {
  protocol.handle('app', (request) => {
    const url = new URL(request.url)
    // 只允许访问打包目录内的文件
    const filePath = path.join(app.getAppPath(), 'dist', url.pathname)
    return new Response(fs.readFileSync(filePath))
  })
})

1-6 Fuses 与默认项复查

Electron 还提供一组「Fuses(熔断开关)」,用 @electron/fuses 可以在打包后开启或关闭某些能力。比如 runAsNodenodeCliInspect 允许应用在被命令行参数或环境变量触发时表现出不同行为,存在被外部脚本借你的权限执行命令的风险。若你的应用不需要这些能力,应当在构建阶段把它们熔断关闭。

把前面几类漏洞串起来看,你会发现它们大多能在「默认安全配置 + 最小权限 + 输入校验」这三条原则下被化解。下一章我们转到更轻松的话题:如何把 React、Vue 这类前端框架接入 Electron。

小结

Electron 里最致命的几类漏洞,本质都来自「把不该暴露的能力暴露给了不可信代码」。XSS 配 Node 集成等于 RCE;原型链污染来自无防护的对象合并;命令注入来自 shell 拼接;供应链与本地文件暴露则来自疏忽的默认项。落实 contextIsolationnodeIntegration: falsesandbox,再配合 CSP、校验与自定义协议,就能挡住绝大多数风险。