mip-sandbox
mip-sandbox 是一系列跟 MIP 沙盒相关的工具,包括沙盒对象定义、代码沙盒检测工具、代码沙盒替换工具等等。
安装
通过 npm
进行安装:
npm install --save-dev mip-sandbox
使用
沙盒对象
var sandbox = require('mip-sandbox')
sandbox.setTimeout(function () {}, 3000)
sandbox.document.cookie
不安全全局变量检测
使用 mip-sandbox/lib/unsafe-detect
方法进行不安全全局变量检测,该函数的定义如下
使用例子如下:
var detect = require('mip-sandbox/lib/unsafe-detect')
var keywords = require('mip-sandbox/lib/keywords')
var code = `
var a = 1
console.log(b)
`
var results = detect(code, keywords.WHITELIST)
console.log(result)
不安全全局变量替换
使用 mip-sandbox/lib/generate
方法进行不安全全局变量替换,该函数的定义如下
使用例子如下:
var generate = require('mip-sandbox/lib/generate')
var keywords = require('mip-sandbox/lib/keywords')
var code = `
var a = 1
console.log(b)
window.console.log(a)
`
var result = generate(code, keywords.WHITELIST_RESERVED)
console.log(result)
对于严格模式下 window 需要替换成 MIP.sandbox.strict.window
在这种情况下,需要传入第三个参数:
options.prefix
默认的 options.prefix === 'MIP.sandbox',在严格下可以传入 MIP.sandbox.strict,得到的结果将如下所示:
var result = generate(code, keywords.WHITELIST_STRICT_RESERVED, {prefix: 'MIP.sandbox.strict'})
该方法使用 escodegen 实现的 ast to string
该方法的第三个参数 options.escodegen 将会透传给 escodegen 因此比如需要返回 sourcemap 的话,请于第二个参数传入 sourcemap 相关参数
如:
var output = generate(code, keywords.WHITELIST_RESERVED, {
escodegen: {
sourceMap: 'name',
sourceMapWithCode: true
}
})
对于不需要生成 sourceMap 的情况,可以使用 generate-lite 来去掉 source-map 相关代码以减小打包体积。
该方法的定义如下:
var generate = require('mip-sandbox/lib/generate-lite')
var keywords = require('mip-sandbox/lib/keywords')
var code = generate(code, keywords.WHITELIST_RESERVED)
沙盒检测替换优化
在某些场景下需要同时使用 detect 和 generate 去实现功能,这时,如果对这两个方法传入的 code 都是字符串的话,就需要对字符串做两次 ast 解析和标记,为了解决这个问题,可以调用 global-mark 生成解析标记好的 ast,再将 ast 传入 detect 和 generate 中,从而提高效率:
var mark = require('mip-sandbox/lib/global-mark')
var detect = require('mip-sandbox/lib/unsafe-detect')
var generate = require('mip-sandbox/lib/generate')
var keywords = require('mip-sandbox/lib/keywords')
var ast = mark(code)
var unsafeList = detect(ast, keywords.WHITELIST)
var generated = generated(ast, keywords.WHITELIST_RESERVED)
沙盒替换规则
沙盒替换采用白名单机制,即只允许开发者使用部分全局 API,不在白名单里的方法会在编译时自动将相关代码加上 MIP.sandbox
前缀,这样就会导致报错。
比如下面一段代码:
const a = require('path/to/a')
console.log(a)
eval('console.log("b")')
window.console.log(b)
setTimeout(function () {
console.log(this)
}.bind(undefined), 3000)
将会替换成:
const a = require('path/to/a')
console.log(a)
MIP.sandbox.eval('console.log("b")')
MIP.sandbox.window.console.log(MIP.sandbox.b)
setTimeout(function () {
console.log(MIP.sandbox.this(this))
}.bind(undefined), 3000)
解释
上述代码中 require、console、eval、window、setTimeout、undefined、b 属于全局变量,其中 require、console 属于安全全局变量,所以不做任何处理;eval、window、b 属于不安全全局变量,因此会加上 MIP.sandbox
前缀,其中 MIP.sandbox.window 是有定义的,而 MIP.sandbox.eval 和 MIP.sandbox.b 没有定义,因此上述代码会报错。
this 沙盒替换
上述代码中经过对比可以看到 this 被替换成了 MIP.sandbox.this(this),这是因为在类似 function () {}.bind(undefined)
的情况下,函数内的 this 指向 window,而 诸如 document.addEventListener('scroll', function () {})
,的回调里的 this 指向 document,这些都是不安全全局变量,因此需要 MIP.sandbox.this() 方法将 window 和 document 替换掉:
MIP.sandbox.this = function (that) {
return that === window ? MIP.sandbox : that === document ? MIP.sandbox.document : that
}
严格模式
在 mip-script 中,理论上只允许进行数据运算和发请求等等操作,不允许直接操作 DOM ,因此在 mip-script 中写的 js 将会以沙盒的严格模式进行全局变量替换,比如 window 会被替换成 MIP.sandbox.strict.window
、 this 将会替换成 MIP.sandbox.strict.this(this)
。
其中 MIP.sandbox.strict 是 MIP.sandbox 的子集。
可用全局变量
以下变量是 MIP sandbox 暴露给用户可直接使用的全局变量,后续会根据实际需要进行增加或减少。
原生安全的全局变量
以下全局变量被认为是安全的,在沙盒注入过程中不会加上任何沙盒前缀。
var ORIGINAL = [
'Array',
'ArrayBuffer',
'Blob',
'Boolean',
'DOMError',
'DOMException',
'DataView',
'Date',
'Error',
'Float32Array',
'Float64Array',
'FormData',
'Headers',
'Infinity',
'Int16Array',
'Int32Array',
'Int8Array',
'JSON',
'Map',
'Math',
'NaN',
'Number',
'Object',
'Promise',
'Proxy',
'ReadableStream',
'ReferenceError',
'Reflect',
'RegExp',
'Request',
'Response',
'Set',
'String',
'Symbol',
'SyntaxError',
'TextDecoder',
'TextEncoder',
'TypeError',
'URIError',
'URL',
'URLSearchParams',
'Uint16Array',
'Uint32Array',
'Uint8Array',
'Uint8ClampedArray',
'WebSocket',
'WritableStream',
'crypto',
'console',
'decodeURI',
'decodeURIComponent',
'localStorage',
'navigator',
'sessionStorage',
'screen',
'undefined',
'devicePixelRatio',
'innerHeight',
'innerWidth',
'isSecureContext',
'length',
'outerHeight',
'outerWidth',
'screenLeft',
'screenTop',
'screenX',
'screenY',
'scrollX',
'scrollY',
'mipDataPromises',
'atob',
'clearInterval',
'clearTimeout',
'encodeURI',
'encodeURIComponent',
'escape',
'fetch',
'getComputedStyle',
'isFinite',
'isNaN',
'matchMedia',
'parseFloat',
'parseInt',
'setInterval',
'setTimeout',
'unescape',
'fetchJsonp'
]
var RESERVED = [
'arguments',
'require',
'module',
'exports',
'define',
'import',
'process'
]
普通模式的沙盒安全变量
普通模式下的沙盒安全变量包含原生安全变量的全部,并且添加了以下变量:
var WHITELIST_ORIGINAL = [
...ORIGINAL,
...RESERVED,
'CustomEvent',
'File',
'FileList',
'FileReader',
'Image',
'ImageBitmap',
'MutationObserver',
'Notification',
'history',
'location',
'scrollbars',
'addEventListener',
'cancelAnimationFrame',
'createImageBitmap',
'removeEventListener',
'requestAnimationFrame',
'scrollBy',
'scrollTo',
'scroll',
'webkitCancelAnimationFrame',
'webkitRequestAnimationFrame',
]
var WHITELIST_CUSTOM = [
{
name: 'document',
properties: [
'domain',
'head',
'body',
'title',
'cookie',
'referrer',
'readyState',
'documentElement',
'createDocumentFragment',
'execCommand',
'getElementById',
'getElementsByClassName',
'getElementsByTagName',
'querySelector',
'querySelectorAll',
{
name: 'createElement',
getter: function () {
return function (nodename) {
if (typeof nodename === 'string' && nodename.toLowerCase()) {
console.error('[MIP] 禁止创建 SCRIPT 标签引入第三方 JS 脚本')
}
return document.createElement(nodename)
}
}
}
]
},
{
name: 'window',
getter: 'MIP.sandbox'
},
{
name: 'MIP',
getter: 'MIP'
}
]
因此,白名单的定义为,普通模式下的全局安全变量与自定义安全变量的集合:
var WHITELIST = WHITELIST_ORIGINAL.concat(WHITELIST_CUSTOM.map(item => item.name))
var WHITELIST_RESERVED = WHITELIST_ORIGINAL
严格模式下的沙盒安全变量
严格模式下的沙盒安全变量包含原生安全变量的全部,并且添加了以下变量:
var WHITELIST_STRICT_ORIGINAL = [
...ORIGINAL,
...RESERVED
]
var WHITELIST_STRICT_CUSTOM = [
{
name: 'document',
properties: [
'cookie',
'domain'
]
},
{
name: 'location',
access: 'readonly',
properties: [
'href',
'protocol',
'host',
'hostname',
'port',
'pathname',
'search',
'hash',
'origin'
]
},
{
name: 'MIP',
access: 'readonly',
properties: [
'watch',
'setData',
'viewPort',
'util',
'sandbox',
{
name: 'viewer',
access: 'readonly',
properties: [
'isIframed',
'sendMessage',
'open'
]
},
'MIP_ROOT_PAGE'
]
},
{
name: 'window',
getter: 'MIP.sandbox.strict'
}
]
因此,严格模式下白名单的定义为,普通模式下的全局安全变量与自定义安全变量的集合:
var WHITELIST_STRICT = WHITELIST_STRICT_ORIGINAL.concat(WHITELIST_STRICT_CUSTOM.map(item => item.name))
var WHITELIST_STRICT_RESERVED = WHITELIST_STRICT_ORIGINAL