6

It's a well known fact that neither Javascript's eval keyword nor Function objects created from strings should ever for any reason be used to run untrusted code.

However, I'm wondering if ES6 proxies change that. Consider:

let env = {eval: eval};
let proxy = new Proxy(env, { has: () => true });
with(proxy) {eval('...')}

The proxy object pretends to have all possible properties, which means that it blocks the search of higher scopes. Within the with block, any properties not set on env appear undefined, and any global properties set inside the with block are actually set on env.

This seems to allow me to set up a completely controlled and isolated environment for the evaled code to run in. What are the risks?

Here are a few concerns I can see:

  1. Don't put anything that references window, or document, or localStorage, or anything else sensitive, into env.
  2. Don't put any mutable object into env unless you're ok with untrusted code mutating it.
    • Solution: make deep copies if necessary.
  3. Code inside the with block has no access to anything outside it. If it needs things like Math, Object, or String, they have to be put in env - which means these can be modified by malicious code. Even the eval function in my minimal example above can be modified.
    • Solution: Create proxies for these objects to white-list read-only access to specific properties.

As long as you follow these guidelines, is this actually safe? Are there other concerns?

Josh
  • 2,039
  • 3
  • 20
  • 25
  • 2
    `while(true);`. – Bergi Jun 21 '17 at 03:33
  • 1
    You also forgot access to `this` (…`.window`, `.document`, `.anything`) and `(function(){}).constructor("…")()` – Bergi Jun 21 '17 at 03:36
  • @Dandan is [doing a very similar thing](https://stackoverflow.com/q/43989742/1048572). Do you work together? – Bergi Jun 21 '17 at 03:39
  • 2
    Because I have fun with messing around: `({}).constructor.defineProperty({}.constructor.getPrototypeOf({}),"get",{value(){this.has=()=>false;this.get=undefined;},writable:true}); whoops; document.write("I am a bad guy. Good luck!")` – Bergi Jun 21 '17 at 04:12

1 Answers1

3

It is quite easy to break out of this environment, via a number of different ways, some or all of which might possibly be mitigated:

  1. Object, Array, and RegExp literals ({ }, [ ], and /.../) are unimpeded by the Proxy and allow access to (and mutation of) Object.protoype, Array.prototype, and RegExp.prototype. You can, however, lock these with Object.freeze before running your eval.

  2. You must delete env.eval within your evaled string, or else the script can execute global code by renaming the eval function like globalEval = eval;

  3. You cannot prevent the creation of new functions, which may use a global this object: (function () { this.globalFunc(); })(). Possibly enforcing strict mode by appending "use strict"; to your evaled input could eliminate this escape vector.

  4. Any access to the Function constructor (via (a=>a).__proto__.constructor) allows execution of global code. You can delete Function.constructor to prevent this, but there may be other ways to access Function.

apsillers
  • 112,806
  • 17
  • 235
  • 239