怎么加固 Electron preload 脚本防止原型链污染攻击

文章导读
Electron 应用安全不仅依赖上下文隔离,preload 脚本作为桥梁处理渲染进程数据时,必须手动过滤危险键名以防止原型链污染。
📋 目录
  1. A 主进程安全配置
  2. B Preload 数据清洗实现
  3. C 漏洞复现与验证
  4. D 常见坑与排查
  5. E 参考来源
A A

核心结论:Electron 应用安全不仅依赖上下文隔离,preload 脚本作为桥梁处理渲染进程数据时,必须手动过滤危险键名以防止原型链污染。

  • 配置检查:webPreferences 中 contextIsolation 必须为 true,nodeIntegration 必须为 false。
  • 代码防御:在 preload 中暴露 IPC 接口时,使用递归函数过滤 __proto__、constructor 等键,禁止直接合并用户输入对象。
  • 验证重点:不仅测试上下文隔离,还要模拟发送污染数据给 preload,确认不会污染 preload 内部对象或 Node 环境。

主进程安全配置

在创建 BrowserWindow 时,必须显式启用安全设置。以下是推荐的最小化安全配置,可直接替换原有的初始化代码:

const win = new BrowserWindow({
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
    contextIsolation: true,
    nodeIntegration: false,
    sandbox: true
  }
})

注意:开启 sandbox 可能会限制部分 Node API 的使用,需根据业务依赖评估。Electron 官方安全指南明确建议默认开启隔离,这是防御原型链污染的基础环境。

Preload 数据清洗实现

即使开启了上下文隔离,如果 preload 脚本接收到渲染进程传来的对象后,将其与本地对象进行不安全合并(如使用 Object.assign 或 lodash.merge),仍可能导致 preload 环境内的原型污染。建议在 preload 中实现一个安全的数据清洗函数:

function safeClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(safeClone);
  
  const safeObj = {};
  for (const key in obj) {
    // 显式阻断原型污染关键键名
    if (['__proto__', 'constructor', 'prototype'].includes(key)) {
      console.warn(`Blocked dangerous key: ${key}`);
      continue;
    }
    safeObj[key] = safeClone(obj[key]);
  }
  return safeObj;
}

contextBridge.exposeInMainWorld('api', {
  sendData: (data) => {
    // 使用安全克隆而非直接传递
    const safeData = safeClone(data);
    ipcRenderer.send('safe-channel', safeData);
  }
});

该函数递归遍历对象,遇到危险键名直接跳过并警告,确保传入 IPC 通道的数据是纯净的普通对象。

漏洞复现与验证

配置完成后,需验证 preload 是否真正防御了数据层面的污染。单纯测试渲染进程自身的原型污染不足以证明 preload 安全,需测试数据穿过 bridge 后的情况。

验证步骤:

怎么加固 Electron preload 脚本防止原型链污染攻击
  1. 在渲染进程控制台发送包含污染键的数据:
    window.api.sendData({ __proto__: { isAdmin: true } });
  2. 在主进程监听 safe-channel,打印接收到的数据:
    ipcMain.on('safe-channel', (event, data) => {
      console.log('Received:', data);
      console.log('Has isAdmin?', data.isAdmin); // 应为 undefined
    });
  3. 检查 preload 内部状态。如果 preload 中有本地配置对象 config,尝试将接收数据合并进去:
    let config = {};
    // 错误做法:Object.assign(config, data);
    // 正确做法:config = safeClone(data);

如果验证通过,主进程接收到的数据中不应包含 isAdmin 属性,且 preload 内部的 Object.prototype 不应被修改。

常见坑与排查

1. 依赖库风险:preload 脚本中引入的第三方 npm 包也可能存在原型污染漏洞,需定期运行 npm audit 并查看安全公告。

2. 反序列化陷阱:避免在 preload 中直接解析来自渲染进程的 JSON 字符串而不做校验,这可能绕过类型检查。始终优先处理对象结构。

3. 隔离误区:contextIsolation 能防止渲染进程直接访问 Node 原型,但无法防止 preload 逻辑错误地将恶意数据合并到自身对象中。数据清洗是必须的。

4. 旧版本兼容:部分旧版 Electron 插件可能依赖 nodeIntegration,强行关闭会导致功能异常,需评估升级成本或寻找替代方案。

参考来源

  • Electron 官方安全指南,页面标题:Security,URL:https://www.electronjs.org/docs/latest/tutorial/security
  • OWASP 关于原型链污染的通用说明,页面标题:Prototype Pollution,URL:https://owasp.org/www-community/vulnerabilities/Prototype_Pollution