【问题标题】:Electron: How to securely inject global variable into BrowserWindow / BrowserView?Electron:如何安全地将全局变量注入 BrowserWindow / BrowserView?
【发布时间】:2020-01-09 06:36:54
【问题描述】:

我想使用 BrowserView 在 Electron 中加载外部网页。它的 API 与 BrowserWindow 几乎相同。

const currentWindow = remote.getCurrentWindow();
const view = new remote.BrowserView({
  webPreferences: {
    // contextIsolation: true,
    partition: 'my-view-partition',
    enableRemoteModule: false,
    nodeIntegration: false,
    preload: `${__dirname}/preload.js`,
    sandbox: true,
  },
});
view.setAutoResize({ width: true, height: true });
view.webContents.loadURL('http://localhost:3000');

在我的preload.js 文件中,我只是将一个变量附加到全局对象。

process.once('loaded', () => {
  global.baz = 'qux';
});

localhost:3000 上运行的应用是一个 React 应用,它引用如下值:

const sharedString = global.baz || 'Not found';

问题是我必须在创建BrowserView 时注释掉设置contextIsolation: true。这暴露了一个安全漏洞。

是否有可能(一种方式 - 从 Electron 到网页)将变量注入 BrowserView(或 BrowserWindow),同时仍然使用 contextIsolation 使 Electron 环境与对全局环境所做的任何更改隔离通过加载的内容?

更新: 一种可能的方法是拦截网络协议,但我不确定这一点????

app.on('ready', () => {
  const { protocol } = session.fromPartition('my-partition')

  protocol.interceptBufferProtocol('https', (req, callback) => {
    if (req.uploadData) {
      // How to handle file uploads?
      callback()
      return
    }

    // This is electron.net, docs: https://electronjs.org/docs/api/net
    net
      .request(req)
      .on('response', (res) => {
        const chunks = []
        res.on('data', (chunk) => {
          chunks.push(Buffer.from(chunk))
        })
        res.on('end', () => {
          const blob = Buffer.concat(chunks)
          const type = res.headers['content-type'] || []
          if (type.includes('text/html') && blob.includes('<head>')) {
            // FIXME?
            const pos = blob.indexOf('<head>')
            // inject contains the Buffer with the injected HTML script
            callback(Buffer.concat([blob.slice(0, pos), inject, blob.slice(pos)]))
          } else {
            callback(blob)
          }
        })
      })
      .on('error', (err) => {
        console.error('error', err)
        callback()
      })
      .end()
  })
})

【问题讨论】:

  • 如果您的全局变量是一个简单的字符串,为什么不将它作为 url 的查询参数传递,例如:view.webContents.loadURL('http://localhost:3000?baz=qux');。在您的反应应用程序中,您可以解析查询参数。
  • @VaibhavVishal 不是。我想举一个简单的例子,但我需要注入的是一个 Web3 提供者。
  • @J.Hesters 您的 React 应用程序是否已经包含 web3.js 库(如果我正确理解您的情况)?然后你可以传入一个提供者 URL 字符串。您将无法将库作为全局变量从电子传递到启用 contextIsolation 的页面。另外我不确定,您的代码 sn-p protocol.interceptBufferProtocol 与您的案例有什么关系(文件上传?应该拦截什么 https 请求?)。看起来有点像 XY 问题,这可能更容易完成。也许您可以详细说明您的问题。
  • 如果您能够调整 Web 应用程序构建:另一种方法是提供 React Web 应用程序的电子特定版本,它通过添加/捆绑的 web3 功能扩展您的基本 Web 版本。这个增强版本可以包含在您的 Electron 构建中,而 Web 版本保持不变。

标签: javascript reactjs security electron


【解决方案1】:

在进行了一些挖掘之后,我发现了一些针对 Electron 的拉取请求,这些请求详细说明了您遇到的问题。 first 描述了一个与您描述的问题非常相似的可重现示例。

预期行为

https://electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content 预加载脚本应该能够使用 contextIsolation: true 将任何内容附加到窗口或文档。

实际行为

preload.js 中附加到窗口的任何内容都会在渲染器中消失。

似乎最后的评论解释了预期的行为不再有效

实际上直到最近才有可能,一个独立世界的 PR 改变了这种行为。

second 有一位用户提出了他们发现的解决方案:

经过多天的研究和摆弄 IPC,我得出结论,最好的方法是走协议路线。

我查看了 BrowserWindowBrowserView 的文档以及显示您想要的行为的 example,但这些 PR 表明这不再可能(沿着这条路线)。

可能的解决方案

查看文档,您从view.webContents 获得的webContents 对象具有executeJavaScript 函数,因此您可以尝试以下设置全局变量。

...
view.setAutoResize({ width: true, height: true });
view.webContents.loadURL('http://localhost:3000');
view.webContents.executeJavaScript("global.baz = 'qux';");
...

【讨论】:

  • 感谢您的帮助。我要试试executeJavaScript,我会给你提供最有用的答案的赏金。非常感谢。
  • executeJavaScript 对我们不起作用,因为它肯定是在页面加载之后,这并不能解决预加载脚本以定义全局变量的任务。
【解决方案2】:

其他答案已经过时,使用contextBridge一定要使用sendToHost()而不是send()

    // Preload (Isolated World)
    const { contextBridge, ipcRenderer } = require('electron')
    
    contextBridge.exposeInMainWorld(
      'electron',
      {
        doThing: () => ipcRenderer.sendToHost('do-a-thing')
      }
    )
    
    // Renderer (Main World)
    
    window.electron.doThing()

【讨论】:

  • window.electron 未定义。请使用有效代码更新您的答案。
  • @Abbas 代码仍然适用于电子 16,你一定做错了什么
【解决方案3】:

因此,Zapparatus 建议的 executeJavaScript 最终成为解决方案的一部分。

这就是renderer.js 中发生的事情。

view.webContents.executeJavaScript(`
  window.communicator = {
    request: function(data) {
      const url = 'prefix://?data=' + encodeURIComponent(JSON.stringify(data))
      const req = new XMLHttpRequest()
      req.open('GET', url)
      req.send();
    },
    receive: function(data) {
      alert('got: ' + JSON.stringify(data))
    }
  };
`)
const setContent = data => view.webContents.executeJavaScript(
  `window.communicator.receive(${JSON.stringify(data)})`
)
ipcRenderer.on('communicator', (event, message) => {
  setContent(`Hello, ${message}!`)
})

我们最终设置了一个自定义协议,类似于 here 的完成方式。在您的 main.js 文件中设置以下内容:

const { app, session, protocol } = require('electron')
const { appWindows } = require('./main/app-run')
const { URL } = require('url')

protocol.registerSchemesAsPrivileged([
  {
    scheme: 'prefix',
    privileges: {
      bypassCSP: true, // ignore CSP, we won't need to patch CSP
      secure: true // allow requests from https context
    }
  }
])

app.on('ready', () => {
  const sess = session.fromPartition('my-view-partition')

  // https://electronjs.org/docs/tutorial/security#4-handle-session-permission-requests-from-remote-content
  sess.setPermissionRequestHandler((webContents, permission, callback) => {
    // Denies the permissions request
    const decision = false
    return callback(decision)
  })

  sess.protocol.registerStringProtocol('prefix', (req, callback) => {
    const url = new URL(req.url)
    try {
      const data = JSON.parse(url.searchParams.get('data'))
      appWindows.main.webContents.send('prefix', data)
    } catch (e) {
      console.error('Could not parse prefix request!')
    }
    const response = {
      mimeType: 'text/plain',
      data: 'ok'
    }
    callback(response)
  })
})

不需要preload.jspostMessage

【讨论】:

    猜你喜欢
    • 2019-07-02
    • 2016-07-25
    • 1970-01-01
    • 2022-07-13
    • 2018-12-23
    • 1970-01-01
    • 2020-11-20
    • 2010-12-29
    • 2021-03-10
    相关资源
    最近更新 更多