How to safely run user-supplied Javascript code inside the browser?
Asked Answered
B

3

42

Imagine a scenario where I want to continuously invoke user-supplied Javascript code, like in the following example, where getUserResult is a function that some user (not myself) has written:

for (var i = 0; i < N; ++i) {
    var x = getUserResult(currentState);
    updateState(currentState, x);
}

How can I execute that kind of code in a browser and/or Node.js, without any security risks?

More generally, how can I execute a Javascript function that is not allowed to modify or even read the current webpage or any other global state? Is there something like an in-browser "JS virtual machine"?

How does JSFiddle ensure that you cannot run any malicious code (at the very least it could phish your login name, run a bot for the lifetime of the page, if not do much worse things)? Or doesn't it ensure that at all?

Battlement answered 19/3, 2014 at 12:35 Comment(6)
Sounds like a job for Google Caja.Supersonic
@GeorgeStocker I updated my answer with the final solution that I am using. It works in the browser, should also work on Node.Battlement
possible duplicate of How to run untrusted code serverside? for Node.js, #21408843 for browserArgybargy
@CiroSantilli 1) My question is regarding node, as well as the browser. The other one only refers to Node. 2) The answers provided here are of use to me. The other question's answers are only suggest a bunch of Node-only sandbox modules, and some general information that does not really solve the problem.Battlement
This should not have been marked as a duplicate, I was wrong at the time. I actually Googled into this and want the answer for browser haha, also for e-learning graph generation! As a punishment I will now have to answer anything I find by editing your answer and not get rep for it.Argybargy
OK, so what about this one: #195649 :-)Argybargy
B
27

After much consideration and with the help of other posters in this thread (thank you so much for your help!), I found a first bunch of answers to my questions. I am re-writing my answer here though, because it summarizes the concepts and also gives you some actual code to experiment with.

Generally, there are two solutions to this problem: We can either use iframe or Worker to run code in an isolated environment, thus making it impossible to read or write the current page's information (which is my first major security concern).

Caja + Closure

There are more complete sandbox solutions such as Google Caja, which (by default) also runs its code in an iframe. Caja does not only sandbox JS, but also HTML and CSS. However, it does not seem very actively maintained anymore.

Update: Caja has been deprecated since the original posting. It has been replaced with the Closure Toolkit, specifically Closure Library and Closure Templates.

WebWorker + Blacklisting

As proposed by Juan Garcia, I am going with the web worker API, but that is not the complete story. Even though, the worker cannot directly access anything from the hosting page, there are still quite a few security risks. This site lists all built-ins available in a Worker's context.

This JSFiddle demonstrates a way to run a string of code outside the window's context without having to go through the server, which is still unsafe, as pointed out in the comments.

I have extended on that and employed a black-list based approach to disable all outside communication by taking away all of the following:

  • Worker
  • WebSocket
  • XMLHttpRequest
  • importScripts

Webworker + Whitelisting

The actually safest approach however, is a white-list approach, (mentioned here), as it will stay safe into the future (given that the semantics of any whitelisted globals will never change in such a way that they allow more access in any future release; which is a somewhat reasonable assumption).

I ended up implementing the whitelist approach in my in-browser WumpusGame that allows you to write your own AI script while playing the game, a few years back. Source code here. You can see that the Whitelist is maintained in GuestScriptContext while the workers are managed by the HostScriptContext.

The GameScriptContext shows how you can use it.

It has several features, including guest<->host communication, guest can execute non-privileged actions (and if it has a security token even privileged actions) on the host, and a lot more.

NOTE: It does not play well with some extensions, as they will prevent overriding globals (when I try to run it, it complains about TEMPORARY being one of them). The fix would be to either whitelist those, or figure out how to make it play nice with modern browser security features, or just security-focused extensions, such as AdBlock etc.

The game is theoretically playable here, but because of reasons above it does not seem to work in Chrome (and I haven't tried other browsers yet).

Some more references:

Battlement answered 6/4, 2014 at 9:45 Comment(3)
Your current code does not make the web worker environment safe for arbitrary code execution as in the linked thread, so you should not call that "safely running untrusted code"!!!Unending
"thus making it impossible to read or write the current page's information (which is my first major security concern)". No it doesn't. DOM access is required for writing to the current page, yes, so phishing would be prevented, but for reading a simple XHR is enough - notice it's privileged as it's running from your domain! That's a huge XSS/CSRF hole!Unending
@Unending You should read the whole thing: At the end, I am going into security details and even provide a link to a complete solution for making web workers safe. I'll rephrase the answer a little though, since, you are right, it is a bit confusing.Battlement
B
9

You can do this with workers. The best thing about workers is that they run in a different process, so if that user code gets into an infinity loop will not hang your page. The only interface with the worker is a message interface, so strings only are exchanged which is extremely secure but limited in some situations.

As not all currently used browsers support workers, iframes are a helpful alternative. You can create them in javascript, set display to 'none', add them to the document, get the eval function from the iframe contentWindow, then 'destroy' the iframe, like setting the outerHTML to '' (empty string). It is tricky, as sometimes the iframe window get garbage collected, but if you manage to do it right, you will have an eval that has a different global object bound to it, which has an empty document. You can then create an interface with the code running it that global with the code running in your page global. The security depends on how do you implement that interface. You should not expose your global object, that means you should nullify the 'parent' property in the iframe global before running any user code there. You should also make sure you don't pass any Element object from the page document to the code running in the iframe global, or the user running that code will be able to navigate your whole document through the parentElement property, you should wrap them to your Element interface using Object.defineProperty for example. It is a lot of work, it did it once, but it is not sure that will be compatible between browsers. For example, if I well remember and I am not making a mistake, Chrome let me nullify the parent property in the iframe global, while in Safari on an iPad I wasn't able. Sorry I don't have any code to share with you right now, I will post it if I have time. However, iframes scripts run in the same process than the page, which it means that an infinite loop will hang your page scripts at well, and today browser compiling the code to machine instructions can effectively hang the client os (depending of the browser and the os).

Third you can use a sandbox, there are JavaScript self interpreters or like in T.J. Crowder comment compilers that will simplify that for you, but the side effect is that it will run slower, specially interpreters.

IMHO, I think the folks at ECMA should better worry less about ugly arrow syntax and stuff like that and implement a secure way of running process with different global that have an interface an specific document element and its descendants but unable to get that element parent and access to the document. This will effectively enable a way of writing JavaScript plugins and also secure advertisement system.

Bologna answered 19/3, 2014 at 13:26 Comment(4)
How can I communicate between the two? Can I somehow get access to function pointers from either context to the other?Battlement
In the case of workers use the assyncronous message interface. For the iframe context use just syncronous function calls. Interpreters or compilers had their own interface, so check the documentation.Bologna
What about security? Is there no chance for a worker to access state of the window?Battlement
@Battlement Workers have not possible way to access to the window global, they have a different global object and only strings are ever exchanged between themBologna
O
0

There is a safer alternative for use cases where security concerns outweigh performance requirements. You can run untrusted code in a completely isolated QuickJS runtime. QuickJS is a Javascript interpreter that was ported to WebAssembly, so it can be embedded into NodeJS and browsers. There is zero chance that code running in such an environment can access the DOM, user cookies or any web APIs.

Check out sandbox-worker on npm for how to use this approach.

Ozellaozen answered 22/6, 2024 at 16:31 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.