qiankun 沙箱的安全性

问题简述

qiankun 是一个微前端框架,可以有效地隔离主应用及各个子应用之间的运行环境,使得 window 对象不会被污染,但它并不是一个绝对安全的沙箱环境,子应用可以通过一些 “特殊技巧” 绕过沙箱。所以,如果大家遵守一定的约束和规范,qiankun 可以很好发挥作用,否则就会出现很多问题。

手动加载 script

qiankun 会通过 import-html-entry 组件,预先获取 html 文本流,然后分别收集样式表(link 标签和 style 标签)和脚本(script 标签),然后在 proxyWindow 中执行 js 脚本,但是手动加载的脚本不会被收集到:

// app entry
let script = document.createElement('script');
script.innerText = `window.nanote = 'running';`
document.body.append(script);

// main
console.log(window.nanote) // running

也可以将上述代码中的 script.innerText 改为 script.src="example.com/index.js" 来执行更丰富的内容

其他方法

  • qiankun 默认 document 不拦截,所以子应用是可以操作其他应用 DOM
  • 修改内置对象使得其他应用 “中招”

    // app
    Array.isArray = () => true;
    
    // main
    Array.isArray('') // true

此时就突出了 Nanote 使用 iframe 而不是 qiankun 的必要性


一些思考

未来版本的 Nanote 将转向 iframe 方案,但仍然面临着类似的安全问题。

iframe 内部上下文完全可以通过 window.parent 访问主应用的 window 环境,尽管 IPC_API 的私密性问题 已经得到彻底解决,但是子应用仍然可以据此获取、修改主应用的 DOM 以及其他子应用的 DOM,这似乎有点危险 ㊙

因此,要分别为每个子应用设定 proxyWindow 环境,拦截一些敏感的 DOM 操作,这些都可以借助 import-html-entry 的 api 实现:

import importHtmlEntry from 'https://cdn.jsdelivr.net/npm/import-html-entry/+esm'
let appWindow = document.getElementById('app').contentWindow
let proxyWindow = new Proxy(appWindow, {
  get(target, prop, receiver) {
    if (prop === 'parent') return 'can not use window.parent'
    if (prop === 'top') return 'can not use window.top'
    return target[prop]
 }
})
importHtmlEntry('./app.html').then(data => {
  appWindow.document.write(data.template)
  data.execScripts(proxyWindow).then((data) => {
    console.log('finish')
  })
})

但是仍然无法拦截手动加载的脚本对 window 的访问,要想解决这个问题,就必须禁止 <script> 标签的创建,可以通过 Proxy 中的 apply 拦截 createElement,但多少可能会造成一些性能问题,再思考和调研一阵吧,看看有没有更好的办法。