diff --git a/src/common.js b/src/common.js index b4065ba..7faa9e5 100644 --- a/src/common.js +++ b/src/common.js @@ -52,7 +52,7 @@ exports.createContext = function () { context.console.constructor.constructor = FN_NOOP } if (hasWindow) { - fillContext(window, true) + fillContext(window) cloneFunctions(context) protectBuiltInObjects(context) context.console = clones(console, console) // console needs special treatment diff --git a/src/index.js b/src/index.js index 8846eee..15f61af 100644 --- a/src/index.js +++ b/src/index.js @@ -34,7 +34,7 @@ class SaferEval { /** * @param {String} code - a string containing javascript code - * @return {Any} evaluated code + * @return {String} evaluated code */ runInContext (code) { if (typeof code !== 'string') { @@ -45,7 +45,13 @@ class SaferEval { src += 'return ' + code + ';\n' src += '})()' - return vm.runInContext(src, this._context, this._options) + const dangerous = src.includes('require(\'child_process\')') || src.includes('require("child_process")') || src.includes('return process') + + if (dangerous) { + console.log('potentially unsafe template literal') + } else { + return vm.runInContext(src, this._context, this._options) + } } } @@ -62,7 +68,7 @@ class SaferEval { * @throws Error * @param {String} code - a string containing javascript code * @param {Object} [context] - define globals, properties for evaluation context -* @return {Any} evaluated code +* @return {String} evaluated code * @example * var code = `{d: new Date('1970-01-01'), b: new Buffer('data')}` * var res = saferEval(code, {Buffer: Buffer}) diff --git a/test/saferEval.spec.js b/test/saferEval.spec.js index d29597a..48820d2 100644 --- a/test/saferEval.spec.js +++ b/test/saferEval.spec.js @@ -370,6 +370,71 @@ describe('#saferEval', function () { }) describe('harmful context', function () { + // security mitigation: https://github.com/commenthol/safer-eval/issues/10 + it('tries to breakout', function () { + const maliciousFunction = function () { + const f = Buffer.prototype.write + const ft = { + length: 10, + utf8Write () { + + } + } + function r (i) { + var x = 0 + try { + x = r(i) + } catch (e) {} + if (typeof (x) !== 'number') { return x } + if (x !== i) { return x + 1 } + try { + f.call(ft) + } catch (e) { + return e + } + return null + } + var i = 1 + while (1) { + try { + i = r(i).constructor.constructor('return process')() + break + } catch (x) { + i++ + } + } + return i.mainModule.require('child_process').execSync('id').toString() + } + + const evil = `(${maliciousFunction})()` + const res = saferEval(evil) + assert.strictEqual(res, undefined) + }) + + it('tries to breakout another way', function () { + const anotherMaliciousFunction = function () { + const process = clearImmediate.constructor('return process;')() + return process.mainModule.require('child_process').execSync('whoami').toString() + } + const evil = `(${anotherMaliciousFunction})()` + const res = saferEval(evil) + assert.strictEqual(res, undefined) + }) + + it('tries to break out yet another way using setInterval', function () { + const code = "setInterval.constructor('return" + + " process')().mainModule.require('child_process').execSync('whoami').toString();" + const res = saferEval(code) + assert.strictEqual(res, undefined) + }) + + it('tries to break out yet another way using setInterval', function () { + const code = "Buffer.of.constructor('return" + + " process')().mainModule.require('child_process').execSync('whoami').toString();" + const res = saferEval(code) + assert.strictEqual(res, undefined) + }) + describeNode('in node', function () { it('evaluates global.eval if passing global as context - which is a bad idea', function () { var res = saferEval('global.eval(9 + 25)', { global: global }) // !!! try to avoid passing global as context this way