How can I sandbox untrusted user-submitted JavaScr

2019-01-30 04:52发布

问题:

I need to serve user-submitted scripts on my site (sort of like jsfiddle). I want the scripts to run on visitors browsers in a safe manner, isolated from the page they are served on. Since the code is submitted by users, there is no guarantee it is trustworthy.

Right now I can think of three options:

  • Serve the user-submitted content in an iframe from a different domain, and rely on the same-origin policy. This would require setting up an additional domain which I'd like to avoid if possible. I believe this is how jsfiddle does it. The script can still do some damage, changing top.location.href for example, which is less than ideal. http://jsfiddle.net/PzkUw/
  • Use the sandbox attribute. I suspect this is not well supported across browsers.
  • Sanitize the scripts before serving them. I would rather not go there.

Are there any other solutions, or recommendations on the above?

Update

If, as I suspect, the first option is the best solution, what can a malicious script do other than change the top window location, and how can I prevent this? I can manipulate or reject certain scripts based on static code analysis but this is hard given the number of ways objects can be accessed and the difficulty analysing javascript statically in general. At the very least, it would require a full-blown parser and a number of complex rules (some, but I suspect not all, of which are present in JSLint).

回答1:

Create a well defined message interface and use JavaScript Web Worker for the code you want to sandbox. HTML5 Web Workers

Web Workers do not have access to the following DOM objects.

  • The window object

  • The document object

  • The parent object

So they can't redirect your page or alter data on it.

You can create a template and a well defined messaging interface so that users can create web worker scripts, but your script would have the final say on what gets manipulated.

EDIT Comment by Jordan Gray plugging a JavaScript library that seems to do what I described above. https://github.com/eligrey/jsandbox



回答2:

Some ideas of tools that could be helpful in your application - they attack the problem from two different directions: Caja compiles the untrusted JavaScript code to something that is safe while AdSafe defines a subset of JavaScript that is safe to use.

Caja

Caja

The Caja Compiler is a tool for making third party HTML, CSS and JavaScript safe to embed in your website. It enables rich interaction between the embedding page and the embedded applications. Caja uses an object-capability security model to allow for a wide range of flexible security policies, so that your website can effectively control what embedded third party code can do with user data.

AdSafe

AdSafe

ADsafe makes it safe to put guest code (such as third party scripted advertising or widgets) on a web page. ADsafe defines a subset of JavaScript that is powerful enough to allow guest code to perform valuable interactions, while at the same time preventing malicious or accidental damage or intrusion. The ADsafe subset can be verified mechanically by tools like JSLint so that no human inspection is necessary to review guest code for safety. The ADsafe subset also enforces good coding practices, increasing the likelihood that guest code will run correctly.



回答3:

As mentioned, the sandbox attribute of the iframe is already supported by major browsers, but I would additionally suggest a mixed solution: to start a web-worker inside the sandboxed iframe. That would give a separate thread, and protect event the sandboxed iframe's DOM from the untrusted code. That is how my Jailed library works. Additionally you may workaround any restrictions by exporting any set of functions into the sandbox.



回答4:

If you want to sandbox some piece of code by removing it's access to say the window, document and parent element you could achieve it by wrapping it in a closure where these are local empty variables:

(function(window, document, parent /* Whatever you want to remove */){
  console.log(this);      // Empty object
  console.log(window);    // undefined
  console.log(document);  // undefined
  console.log(parent);    // undefined
}).call({});

Calling it with an empty object is important because otherwise this will point to the window object