fix: serverless vm2 safe-eval

This commit is contained in:
Innei
2022-03-14 14:50:39 +08:00
parent 389fe6babe
commit c20cc595e2
6 changed files with 56 additions and 33 deletions

View File

@@ -124,6 +124,7 @@
"rxjs": "7.5.5",
"snakecase-keys": "5.1.2",
"ua-parser-js": "1.0.2",
"vm2": "3.9.9",
"xss": "1.0.11",
"zx": "4.3.0"
},

16
pnpm-lock.yaml generated
View File

@@ -111,6 +111,7 @@ specifiers:
tsconfig-paths: 3.13.0
typescript: 4.6.2
ua-parser-js: 1.0.2
vm2: 3.9.9
webpack-node-externals: 3.0.0
xss: 1.0.11
zx: 4.3.0
@@ -177,6 +178,7 @@ dependencies:
rxjs: 7.5.5
snakecase-keys: 5.1.2
ua-parser-js: 1.0.2
vm2: 3.9.9
xss: 1.0.11
zx: 4.3.0
@@ -2317,6 +2319,11 @@ packages:
engines: {node: '>=0.4.0'}
dev: true
/acorn-walk/8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
dev: false
/acorn/7.4.1:
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
engines: {node: '>=0.4.0'}
@@ -8167,6 +8174,15 @@ packages:
extsprintf: 1.3.0
dev: false
/vm2/3.9.9:
resolution: {integrity: sha512-xwTm7NLh/uOjARRBs8/95H0e8fT3Ukw5D/JJWhxMbhKzNh1Nu981jQKvkep9iKYNxzlVrdzD0mlBGkDKZWprlw==}
engines: {node: '>=6.0'}
hasBin: true
dependencies:
acorn: 8.7.0
acorn-walk: 8.2.0
dev: false
/w3c-hr-time/1.0.2:
resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
dependencies:

View File

@@ -56,12 +56,14 @@ const remoteModule =
受信任的三方库,可在 `snippet.service.ts` 中找到。
**注意**:这是一个完全的执行上下文,你不能编写某些在 NodeJS 运行时正常执行的代码。
**注意**:这是一个完全隔离(可能存在逃逸,请及时指出)的执行上下文,你不能编写某些在 NodeJS 运行时正常执行的代码。
比如: `process` 中只有只读的 env 可以获取,其他方法都被移除; `setTimeout` 等 API 被移除。但是你可以在独立模块中使用这些 API需要注意内存泄漏和安全性。
`require(id, useCache)` require 支持第二个参数,默认为 true这是 NodeJS 的默认行为,可以设定为 `false` 以禁用 `require` 的缓存,但是会增加性能开销。
**注意**:你仍然可以在独立模块中使用主线程的 `require` 方法,所以这并不是一个真正隔离的环境。在使用第三方模块和请注意安全。请不要使用不受信任的模块。
## `Context`
`handler` 函数的第一个参数接受一个全局上下文对象。

View File

@@ -273,7 +273,17 @@ export class ServerlessService {
// }
// fin. is built-in module
const module = isBuiltinModule(id, ['fs', 'os', 'child_process', 'sys'])
const module = isBuiltinModule(id, [
'fs',
'os',
'child_process',
'sys',
'process',
'vm',
'v8',
'cluster',
'fs/promises',
])
if (!module) {
throw new Error(`cannot require ${id}`)
} else {

View File

@@ -1,34 +1,22 @@
import { customAlphabet } from 'nanoid'
import vm from 'vm'
const nanoid = customAlphabet(
'0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$',
16,
)
export function safeEval(
code: string,
context = {},
opts?: string | vm.RunningScriptOptions,
) {
const sandbox = {}
const resultKey = 'SAFE_EVAL_' + nanoid()
sandbox[resultKey] = {}
const clearContext = `
(function() {
Function = undefined;
const keys = Object.getOwnPropertyNames(this).concat(['constructor']);
keys.forEach((key) => {
const item = this[key];
if (!item || typeof item.constructor !== 'function') return;
this[key].constructor = undefined;
});
})();
`
code = clearContext + resultKey + '=' + `((() => { ${code} })())`
import vm2 from 'vm2'
export function safeEval(code: string, context = {}) {
const sandbox = {
global: {},
}
code = `((() => { ${code} })())`
if (context) {
Object.keys(context).forEach(function (key) {
sandbox[key] = context[key]
})
}
vm.runInNewContext(code, sandbox, opts)
return sandbox[resultKey]
const VM = new vm2.VM({
timeout: 60_0000,
sandbox,
eval: false,
})
return VM.run(code)
}

View File

@@ -12,9 +12,7 @@ describe.only('test safe-eval', () => {
})
it('should can not access to global or process or require', () => {
expect(() => {
safeEval(`return global`)
}).toThrow()
expect(safeEval(`return global`)).toStrictEqual({})
expect(() => {
safeEval(`return process`)
@@ -25,6 +23,14 @@ describe.only('test safe-eval', () => {
}).toThrow()
})
describe('test escape', () => {
it('case1', () => {
expect(() =>
safeEval(`this.constructor.constructor("return process")().exit()`),
).toThrow()
})
})
it('should can access mocked global context', () => {
const res = safeEval(`return global.a`, { global: { a: 1 } })
expect(res).toBe(1)