Introduction
In certain circumstances, you may want to evaluate JavaScript code with restricted privileges. In Firefox 1.5 (Gecko 1.8) or later, an API exists to allow you to do this. It contains the notion of a "sandbox" that you can create and evaluate code in its context. Code evaluated using this method will always execute with restricted privileges, as on a normal web page.
Use
To use evalInSandbox()
, you must first create a sandbox object using its constructor, Components.utils.Sandbox
. The sandbox must be initialized with either an origin URI or preferably a DOM window (nsIDOMWindow, like the window
object in a web page). In Firefox 3 (Gecko 1.9) or later, it can also be initialized with an nsIPrincipal
object. Using an nsIPrincipal
is preferable to using an origin URI. This URI, window, or nsIPrincipal
is used as the origin of the string being evaluated for all security purposes (e.g. for same-origin security checks). For example, passing a URI of https://www.example.com/
will allow code evaluated using this sandbox to make AJAX requests from https://www.example.com. If the JavaScript you run in the sandbox sets document.domain
, that is it changes the same-origin security checks, you should pass a DOM window object or nsIPrincipal
to the sandbox constructor rather than a URI.
Here is an example with a URI:
// create a sandbox with a given origin URI var s = Components.utils.Sandbox("https://www.example.com/");
The sandbox is just an empty JavaScript object marked as being created by the restricted privilege principal. It will become the global scope object when you pass it to evalInSandbox(text, sandbox)
. You may want to add objects to this scope. Continuing the example:
s.y = 5; // insert property 'y' with value 5 into global scope. var result = Components.utils.evalInSandbox("x = y + 2; x + 3", s); // result is 10, s.x is now 7
Operations on objects you insert into this sandbox global scope do not carry privileges into the sandbox:
s.foo = Components; // this will give a "Permission Denied" error Components.utils.evalInSandbox("foo.classes", s);
On the other hand, any function that comes out of the sandbox executes with the privileges of chrome code. This can happen in a surprising number of ways as you can see in the next section.
Note bug 350558.
Security
evalInSandbox()
if you rely on the properties of an object resulting from the code executed in the sandbox.Some examples:
<script src="prototype.js"></script> <script> var s = new Components.utils.Sandbox(url); var x = Components.utils.evalInSandbox(untrusted_code, s); if (x == 1) { /* this code is unsafe; calls x.valueOf() */ } if (x === 1) { /* this code is safe */ } var y = x.y; /* this is unsafe */ var z = sandbox.z; /* unsafe */ if (typeof x == "number") { /* safe */ } </script>
To understand how the security risk arises, let's take the example of (x == 1)
. When this is evaluated, the x.valueOf()
method gets called by chrome code. When this happens, unsafe code can create a String object in the chrome code's global scope.
If the chrome code has added a function that has chrome privileges to String.prototype
, Object.prototype
, or Function.prototype
, then unsafe code can abuse that function. What the unsafe code can do depends on what that function is, but the unsafe code can wind up being executed with chrome privileges.
To avoid this potential security risk, you should either carefully avoid using objects resulting from evalInSandbox()
in any way that might result in a function in that object being called, or you should make sure that untrusted JSON strings don't contain any functions or expressions before using evalInSandbox()
to evaluate them.
See also:
PiggyBank analysis of sandbox