Eval, literal eval, safe eval

# Michał Wadas (10 years ago)

Introdution:

  • eval executes piece of code
  • eval can not be safely used with external input
  • Python's ast.literal_eval would be almost useless in modern JavaScript (almost all data types can be easily send as JSON)

literal_eval description:

The string or node provided may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts, booleans, and None.

My proposition is "safe eval". Safe eval ( eval.safe(string: code, callback) ) should perform theses steps:

  • Create isolated realm without capabilities to perform almost any IO (implementation dependant - no XHR, no importScript, no require)
  • evaluate code in context of created realm
  • post result of last evaluated expression back to creator realm using structured-clone algorithm
  • call callback with returned data

Pros:

  • sandbox offered by language
  • easy to run in other thread
  • quite easy to polyfill
  • servers can send computations to users

Cons:

  • Realm creation can be costly (but implementations can solve this problem in many ways)
  • proposal does not include support for asynchronous operations
# Florian Bösch (10 years ago)

On Sun, Nov 23, 2014 at 12:27 PM, Michał Wadas <michalwadas at gmail.com>

wrote:

literal_eval description:

The string or node provided may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts, booleans, and None.

Also known as JSON.parse.

My proposition is "safe eval". Safe eval ( eval.safe(string: code, callback) ) should perform theses steps:

  • Create isolated realm without capabilities to perform almost any IO (implementation dependant - no XHR, no importScript, no require)
  • evaluate code in context of created realm
  • post result of last evaluated expression back to creator realm using structured-clone algorithm
  • call callback with returned data

Pros:

  • sandbox offered by language
  • easy to run in other thread
  • quite easy to polyfill
  • servers can send computations to users

Cons:

  • Realm creation can be costly (but implementations can solve this problem in many ways)
  • proposal does not include support for asynchronous operations

evalSandboxed would perhaps be a better term.

Btw. you can do a sort of sandboxed eval today by overriding all names found in window. There are some caveats however. The so sandboxed code can still access (and change) object internals, such as "mystring".constructor.prototype.asdf = function(){ console.log("gotcha"); }.

A sandboxes primary purpose isn't just to restrict access to global symbols, it's also to prevent it from corrupting the surrounding codes internals. One way to do that of course would be to have the so sandboxed code run in total isolation on a separate VM instance, however there's some issues with that too.

It would often be the case that you'd want to provide an interface to the sandboxed code. So sandboxing alone isn't quite sufficient, you'd also need to have a suitable API/syntax to describe interfaces, safe to pass to the sandboxed code, such that the sanboxed code cannot corrupt any piece of those interfaces.

# Mark S. Miller (10 years ago)

code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses, research.google.com/pubs/pub40673.html, code.google.com/p/google-caja/wiki/SES, www-cs-students.stanford.edu/~ataly/Papers/sp11.pdf

strawman:concurrency desperately needs updating in light of modern promises, but see discussion of Vats and "there".

On Sun, Nov 23, 2014 at 3:27 AM, Michał Wadas <michalwadas at gmail.com> wrote:

Introdution:

  • eval executes piece of code
  • eval can not be safely used with external input
  • Python's ast.literal_eval would be almost useless in modern JavaScript (almost all data types can be easily send as JSON)

literal_eval description:

The string or node provided may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts, booleans, and None.

My proposition is "safe eval". Safe eval ( eval.safe(string: code, callback) ) should perform theses steps:

  • Create isolated realm without capabilities to perform almost any IO (implementation dependant - no XHR, no importScript, no require)

y

  • evaluate code in context of created realm

y

  • post result of last evaluated expression back to creator realm using structured-clone algorithm

n. Structured clone sucks.

  • call callback with returned data

Prefer promises to callbacks

Pros:

  • sandbox offered by language

y. Plan is to refine Realm API for ES7 by trying to redo SES in terms of Vats.

  • easy to run in other thread

y

  • quite easy to polyfill

Well, it wasn't as easy as I first expected, but we do have a SES polyfill. Not yet for Vats or Dr. SES

  • servers can send computations to users

y

Cons:

  • Realm creation can be costly (but implementations can solve this problem in many ways)

y

  • proposal does not include support for asynchronous operations

Dr. SES does.

# Mark S. Miller (10 years ago)

On Sun, Nov 23, 2014 at 3:38 AM, Florian Bösch <pyalot at gmail.com> wrote:

On Sun, Nov 23, 2014 at 12:27 PM, Michał Wadas <michalwadas at gmail.com> wrote:

literal_eval description:

The string or node provided may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts, booleans, and None.

Also known as JSON.parse.

My proposition is "safe eval". Safe eval ( eval.safe(string: code, callback) ) should perform theses steps:

  • Create isolated realm without capabilities to perform almost any IO (implementation dependant - no XHR, no importScript, no require)
  • evaluate code in context of created realm
  • post result of last evaluated expression back to creator realm using structured-clone algorithm
  • call callback with returned data

Pros:

  • sandbox offered by language
  • easy to run in other thread
  • quite easy to polyfill
  • servers can send computations to users

Cons:

  • Realm creation can be costly (but implementations can solve this problem in many ways)
  • proposal does not include support for asynchronous operations

evalSandboxed would perhaps be a better term.

SES uses the term "confine", as that's what it does -- much better than sandboxing.

Btw. you can do a sort of sandboxed eval today by overriding all names found in window. There are some caveats however. The so sandboxed code can still access (and change) object internals, such as "mystring".constructor.prototype.asdf = function(){ console.log("gotcha"); }.

Not in SES, by use of Object.freeze, etc, to freeze the primordials of the SES realm on initialization. We have a design and partial implementation for CES -- Confined EcmaScript, which is like SES except the primordials are not frozen. As a polyfill, this wasn't worth completing. With Realm API support, perhaps we'll revisit.

A sandboxes primary purpose isn't just to restrict access to global symbols, it's also to prevent it from corrupting the surrounding codes internals.

Among other things, yes.

One way to do that of course would be to have the so sandboxed code run in total isolation on a separate VM instance, however there's some issues with that too.

It would often be the case that you'd want to provide an interface to the sandboxed code. So sandboxing alone isn't quite sufficient, you'd also need to have a suitable API/syntax to describe interfaces, safe to pass to the sandboxed code, such that the sanboxed code cannot corrupt any piece of those interfaces.

See SES.

# Mark Miller (10 years ago)

On Sun, Nov 23, 2014 at 8:22 AM, Mark S. Miller <erights at google.com> wrote:

code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses, research.google.com/pubs/pub40673.html, code.google.com/p/google-caja/wiki/SES, www-cs-students.stanford.edu/~ataly/Papers/sp11.pdf

strawman:concurrency desperately needs updating in light of modern promises, but see discussion of Vats and "there".

See also the two talks announced at www.eros-os.org/pipermail/cap-talk/2011-November/015079.html

# Thaddee Tyl (10 years ago)

On Sun, Nov 23, 2014 at 12:38 PM, Florian Bösch <pyalot at gmail.com> wrote:

Btw. you can do a sort of sandboxed eval today by overriding all names found in window. There are some caveats however. The so sandboxed code can still access (and change) object internals, such as "mystring".constructor.prototype.asdf = function(){ console.log("gotcha"); }.

You can do a lot more. You can prevent the issues you point out (and many others) by mocking all built-in object's prototypes that you want to feed the sandbox (so that modifications are actually performed on a throwaway prototype), and by removing all new properties that weren't there before and replacing those that were there before.

I have an implementation of such a sandbox in ES5.1 here, and a testing ground there. It does use eval(). And Function(). No Realm creation.

For all purposes, it is a safe eval. I really wish that Chrome would use it in their DevTools; I broke them so many times while writing this library that I have lost count. They actually use no sandbox at all in their js console.

A sandboxes primary purpose isn't just to restrict access to global symbols, it's also to prevent it from corrupting the surrounding codes internals. One way to do that of course would be to have the so sandboxed code run in total isolation on a separate VM instance, however there's some issues with that too.

It would often be the case that you'd want to provide an interface to the sandboxed code. So sandboxing alone isn't quite sufficient, you'd also need to have a suitable API/syntax to describe interfaces, safe to pass to the sandboxed code, such that the sanboxed code cannot corrupt any piece of those interfaces.

Most of that is (laborious but) easy. The thorny bit in localeval is passing values to the sandbox. While it obviously works in the version without a timeout (although infinite loops aren't protected against), the version with a timeout, which relies on web workers (or processes in node), doesn't pass non-JSON-serializable values fine.

# Michał Wadas (10 years ago)

This "library" (localeval) is almost useless and potentially dangerous. There are thousands ways to break it's encapsulation and attacker will use all of them.

localeval('('+function a(){ Function('this.a = 3')() }+'())'); window.a; // 3

localeval('('+function a(){ this.a = 5 }+'())') window.a; // 5

localeval('('+function a(){ ({}).constructor.getOwnPropertyNames = function(){return []} }+'())') // Oh, it do not return globals to their natural state.

Possible ways to attack and break sandbox:

  • getters/setters
  • setTimeout and any asynchronous function
  • forcing document reload or something like that
  • throwing error (possibly with getter/setter)

Even worker implementation can do potentially dangerous actions like retrieving data from site using XHR and then transmitting it over web socket to attacker.

2014-11-30 1:41 GMT+01:00 Thaddee Tyl <thaddee.tyl at gmail.com>:

# Florian Bösch (10 years ago)

On Sun, Nov 30, 2014 at 1:41 AM, Thaddee Tyl <thaddee.tyl at gmail.com> wrote:

You can do a lot more. You can prevent the issues you point out (and

many others) by mocking all built-in object's prototypes that you want

to feed the sandbox (so that modifications are actually performed on a throwaway prototype), and by removing all new properties that weren't there before and replacing those that were there before.

It was exemplary. Securing code is actually quite tricky unless you control the VM it runs in. I believe a previous comment pointed this fact out.

For all purposes, it is a safe eval. I really wish that Chrome would use it in their DevTools; I broke them so many times while writing this library that I have lost count. They actually use no sandbox at all in their [js console].

Could you elaborate on that point with some examples how you broke it?

Most of that is (laborious but) easy. The thorny bit in localeval is passing values to the sandbox. While it obviously works in the version without a timeout (although infinite loops aren't protected against), the version with a timeout, which relies on web workers (or processes in node), doesn't pass non-JSON-serializable values fine.

You don't really want to serialize/deserialize everything. It is imminently useful to be able to pass an object or function to a sandbox or receive one out of it. Again, this is possible to do, but it's nearly impossible if you're not in control of the VM (as is evidenced by your own implementation).

# Thaddee Tyl (10 years ago)

Michał: Thanks for pointing this out. Strict mode doesn't quite work like I expected it to. I wonder if anything can be done for that Function('')() behaviour. (Weirdly, I was able to fix the function(){ this.a } behaviour.)

Additionally, this technique, at least for now, cannot be applied to the global object: it leads to strange errors like 'NS_ERROR_NOT_AVAILABLE: prompt aborted by user' emanating from nsPrompter.js in Firefox. It does work fine in Chrome, although everything on the page gets reset, the page gets redrawn, even the devtools seem to reload. Also, running a simple Function('1+1')() results in a weird "TypeError: object is not a function", which is odd, considering that Function instanceof Function is true. (That said, Function.name returns 'Object'.)

Florian: it turns out that the Chrome DevTools did eventually rely on a more robust sandbox, not sure when. A simple ([]).proto.push reset used to suffice.

# Michał Wadas (10 years ago)

Creating secure implementation of eval without creating your own interpreter (or sophisticated operations on AST) is almost impossible - it would require to copy whole environment and provide mocks to any possibly dangerous function. At least O(n^2) complexity without ES6 Map.

# Florian Bösch (10 years ago)

A proper solution really is a separate VM, that isolates the complete environment watertight and by default denies all interaction except for those which have been defined as interaction points (alas it would also see to it a DOS attack with a while(1){} appropriately times out).

Anything else is really just a hack with security holes waiting to be discovered.

# Michał Wadas (10 years ago)

WebWorker with blocked XHR, importScript, WebSockets, creating new WebWorkers should be appropriate and secure against while(true) ;

# Florian Bösch (10 years ago)

well, just because you don't DOS the main thread, doesn't mean making a machine 800% busy isn't a DOS.

# Florian Bösch (10 years ago)

Ah and don't forget that allocating ram till the machine swaps is also a DOS.

# Michał Wadas (10 years ago)

Main thread can terminate worker after eg. 500ms. It is not a problem.