32

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).

Flash
  • 15,945
  • 13
  • 70
  • 98
  • 2
    I think your sub-domain idea is best. I've looked into this question myself and couldn't find a better solution. I once played with loading the scripts dynamically through XmlHttpRequest and running eval -- I don't recall why I dropped it, though. – Jeremy J Starcher Aug 31 '12 at 06:22
  • Related: http://stackoverflow.com/questions/958997/frame-buster-buster-buster-code-needed – Petah Sep 22 '12 at 03:20
  • I agree with @JeremyJStarcher. Regardless, do you have a chance to control certain JS elements, **window** being the most important? If so, I would try to make it reject certain requests made from the iframe but I'm not sure how easy that would be. – inhan Sep 23 '12 at 02:12
  • 1
    [As of now (more than 2 years after you asked your question) browser support for sandbox is pretty good.](http://caniuse.com/#feat=iframe-sandbox) – semicolon Oct 15 '14 at 03:14
  • iframe + sandbox seems to be the recommended solution: https://medium.com/zendesk-engineering/sandboxing-javascript-e4def55e855e – alalonde Mar 19 '18 at 19:59

4 Answers4

26

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

Louis Ricci
  • 20,804
  • 5
  • 48
  • 62
  • Sounds like a good alternative but might be too restrictive. Nevertheless +1 for the idea. – Christoph Sep 25 '12 at 15:02
  • 1
    Good suggestion. My concern is that older browsers don't support this. Also, can't web workers make AJAX requests (meaning I'd still need to serve from a different domain)? – Flash Sep 26 '12 at 08:29
  • @Andrew - What scenario do you see a web worker having AJAX capabilities being an issue? If you're worried about the worker script impersonating the user on your own domain, you could just use cookie-less sessions and avoid that problem. As for browser compatibility, yep, IE9 and below don't support HTML5. – Louis Ricci Sep 26 '12 at 13:10
  • @LastCoder Exactly, the browser would (I assume) send auth cookies with any XHR and then the attacker has access to private info. I would prefer to serve from a different domain than use cookieless sessions as they are prone to leaking (referrer headers, pasting links, etc) - can you think of any reason this wouldn't work? – Flash Sep 26 '12 at 13:19
  • @Andrew - I think creating another sub/domain to host user generated scripts is a completely different problem. Now your session-less, unauthenticated script (2nd) domain could be used to host malicious code for other purposes. JSFiddle avoids these scenarios in 2 ways (that I can see): It's stateless (no login/session) and it writes the HTML, CSS and JS to a src-less IFRAME directly. var w=window.open('');w.document.write(jscode); type of thing. – Louis Ricci Sep 26 '12 at 13:45
  • 1
    Incidentally, you might want to add a link to [jsandbox](https://github.com/eligrey/jsandbox), a sandboxing library that uses this approach. – Jordan Gray Sep 28 '12 at 13:53
  • 2
    Beware you can still do a lot of mess using this technique! You can steal cookies, you can fetch() any url address so you can do CSRF attacks (add me as admin using your admin session) and so on. It is NOT secure at all. – lukyer Jul 04 '17 at 13:27
  • @lukyer - For Cookies: You'd have to have 2 separate domains to mitigate that threat. domainA.tld/page.html, domainB.tld/iframe.html, domainB.tld/webworker.js. Domain A's page.html creates an iframe sources to Domain B's iframe.html, the iframe.html starts domain B's user defined webworker.js. The only difference is domain B's iframe.html has to proxy the PostMessage to Domain A. Assume you authenticate in Domain A, the web worker in Domain B won't have any same origin access to session cookies. – Louis Ricci Jul 06 '17 at 12:11
  • @lukyer - For Fetch() / XHR Abuse: You are absolutely right. Someone could make a malicious web worker that would spam out fetch or xhr requests. – Louis Ricci Jul 06 '17 at 12:20
5

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.

rsp
  • 107,747
  • 29
  • 201
  • 177
  • I have looked into AdSafe. It is designed for scripts that aren't in frames and is therefore very restrictive. For example, you can't do `a[i] = 1`. I think the restrictions may scare the majority of legitimate users. Will have a look at Caja. – Flash Sep 23 '12 at 02:41
  • You can do `a[+i] = 1` though. (This is because when someone writes `a=window; i='ev'+'al'` then `a[i]` would mean `eval` and it would be impossible to statically eliminate that possibility.) But you are right that it may be too restrictive for your needs. – rsp Sep 23 '12 at 02:48
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.

asvd
  • 944
  • 10
  • 16
  • Question about your library, why do you run the code in a sandboxed iframe AND a web worker? Wouldn't it be safe enough to have a normal iframe, and then run the untrusted code in a web worker? The web worker could not access the dom of the iframe, or get it to do anything else really, except for what you do in your onmessage callbacks. Am I correct? – lastmjs Mar 10 '16 at 20:53
  • 3
    Well, not exactly. Initially it ran the code in a web worker only, then I was explained that it is not enough, since a worker still has access to some local origin instances, like local storage, indexeddb, etc. So I had to put a worker in a sandboxed iframe. A worker was still useful to obtain a thread and prevent locking the page with an infinite loop. But later on it appeared that many browsers prohibit running worker in an iframe under https, so now the library only starts a worker if possible, otherwise the code simply runs in an iframe. – asvd Mar 12 '16 at 12:39
-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

DeadAlready
  • 2,988
  • 19
  • 18