mip-sandbox
v1.1.13
Published
sandbox tools for MIP project
Downloads
26
Readme
mip-sandbox
mip-sandbox 是一系列跟 MIP 沙盒相关的工具,包括沙盒对象定义、代码沙盒检测工具、代码沙盒替换工具等等。
安装
通过 npm
进行安装:
npm install --save-dev mip-sandbox
使用
沙盒对象
var sandbox = require('mip-sandbox')
// setTimeout
sandbox.setTimeout(function () {}, 3000)
// document
sandbox.document.cookie
// ...
不安全全局变量检测
使用 mip-sandbox/lib/unsafe-detect
方法进行不安全全局变量检测,该函数的定义如下
/**
* 不安全全局变量检测
*
* @params {string|AST} code 代码字符串或代码 AST
* @params {Array=} keywords 安全全局变量声明列表,
* 在默认情况下,所有全局变量包括 window document 等均认为不安全,
* 需要传入该参数进行条件过滤
* @return {Array.<ASTNode>} 不安全全局变量列表
*/
使用例子如下:
var detect = require('mip-sandbox/lib/unsafe-detect')
var keywords = require('mip-sandbox/lib/keywords')
var code = `
var a = 1
console.log(b)
`
// 严格模式 请使用 keywords.WHITELIST_STRICT
// 在前端使用时,可通过 MIP.sandbox.WHITELIST 去拿该列表
var results = detect(code, keywords.WHITELIST)
console.log(result)
// [
// {
// type: 'Identifier',
// name: 'b',
// range: [...]
// loc: [...]
// }
// ]
不安全全局变量替换
使用 mip-sandbox/lib/generate
方法进行不安全全局变量替换,该函数的定义如下
/**
* 不安全全局变量替换
*
* @param {string|AST} code 代码字符串或代码 AST
* @param {Array=} keywords 安全全局变量声明列表,
* 在默认情况下,所有全局变量包括 window document 等均认为不安全,
* 需要传入该参数进行条件过滤
* @param {Object=} options options
* @param {string=} options.prefix 默认全局变量注入的前缀,默认为 MIP.sandbox
* @param {Object=} options.escodegen 透传给 escodegen 的参数
* @return {string} 替换后的代码字符串
*/
使用例子如下:
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)
// var a = 1
// console.log(MIP.sandbox.b)
// MIP.sandbox.window.console.log(a)
对于严格模式下 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'})
// var a = 1
// console.log(MIP.sandbox.b)
// MIP.sandbox.strict.window.console.log(a)
该方法使用 escodegen 实现的 ast to string
该方法的第三个参数 options.escodegen 将会透传给 escodegen 因此比如需要返回 sourcemap 的话,请于第二个参数传入 sourcemap 相关参数
如:
var output = generate(code, keywords.WHITELIST_RESERVED, {
escodegen: {
sourceMap: 'name',
sourceMapWithCode: true
}
})
// output.code
// output.map
对于不需要生成 sourceMap 的情况,可以使用 generate-lite 来去掉 source-map 相关代码以减小打包体积。
该方法的定义如下:
/**
* 不安全全局变量替换
*
* @param {string|AST} code 代码字符串或代码 AST
* @param {Array=} keywords 安全全局变量声明列表,
* 在默认情况下,所有全局变量包括 window document 等均认为不安全,
* 需要传入该参数进行条件过滤
* @return {string} 替换后的代码字符串
*/
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',
// https://github.com/mipengine/mip2/issues/336
'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',
// https://github.com/mipengine/mip2/issues/347
'TextDecoder',
'TextEncoder',
'TypeError',
'URIError',
'URL',
'URLSearchParams',
'Uint16Array',
'Uint32Array',
'Uint8Array',
'Uint8ClampedArray',
// 1.0.17 新增 WebSocket
'WebSocket',
'WritableStream',
// issue https://github.com/mipengine/mip2/issues/62
'crypto',
'console',
'decodeURI',
'decodeURIComponent',
'localStorage',
'navigator',
'sessionStorage',
'screen',
'undefined',
'devicePixelRatio',
'innerHeight',
'innerWidth',
'isSecureContext',
'length',
'outerHeight',
'outerWidth',
'screenLeft',
'screenTop',
'screenX',
'screenY',
'scrollX',
'scrollY',
// mip-data ready status
'mipDataPromises',
// https://github.com/mipengine/mip2/issues/347
'atob',
'clearInterval',
'clearTimeout',
'encodeURI',
'encodeURIComponent',
'escape',
'fetch',
'getComputedStyle',
'isFinite',
'isNaN',
'matchMedia',
'parseFloat',
'parseInt',
'setInterval',
'setTimeout',
'unescape',
// mip1 polyfill
'fetchJsonp'
]
var RESERVED = [
'arguments',
'require',
'module',
'exports',
'define',
'import',
// process.env.NODE_ENV
'process'
]
普通模式的沙盒安全变量
普通模式下的沙盒安全变量包含原生安全变量的全部,并且添加了以下变量:
// 全局安全变量
var WHITELIST_ORIGINAL = [
...ORIGINAL,
...RESERVED,
// https://github.com/mipengine/mip2/issues/143
'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',
// document 允许使用以下属性或方法
properties: [
// https://github.com/mipengine/mip2/issues/95
'domain',
'head',
'body',
'title',
'cookie',
'referrer',
'readyState',
'documentElement',
'createDocumentFragment',
// https://github.com/mipengine/mip2/issues/470
'execCommand',
'getElementById',
'getElementsByClassName',
'getElementsByTagName',
'querySelector',
'querySelectorAll',
// createElement 禁止创建 SCRIPT 标签
{
name: 'createElement',
getter: function () {
return function (nodename) {
if (typeof nodename === 'string' && nodename.toLowerCase()) {
console.error('[MIP] 禁止创建 SCRIPT 标签引入第三方 JS 脚本')
}
return document.createElement(nodename)
}
}
}
]
},
{
name: 'window',
// window 指向 window.MIP.sandbox
getter: 'MIP.sandbox'
},
{
name: 'MIP',
// MIP 指向 window.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 = [
// 只允许访问 document.cookie
{
name: 'document',
properties: [
'cookie',
// https://github.com/mipengine/mip2/issues/95
'domain'
]
},
// location 只放开读权限
{
name: 'location',
access: 'readonly',
properties: [
'href',
'protocol',
'host',
'hostname',
'port',
'pathname',
'search',
'hash',
'origin'
]
},
// MIP 对象只开放部分工具函数
{
name: 'MIP',
access: 'readonly',
properties: [
'watch',
'setData',
'viewPort',
'util',
'sandbox',
{
name: 'viewer',
access: 'readonly',
properties: [
'isIframed',
'sendMessage',
'open'
]
},
// 'viewer',
'MIP_ROOT_PAGE'
]
},
// 严格模式下的 window 对象指向 MIP.sandbox.strict
{
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