6 Xray vision helps JavaScript running in a privileged security context
7 safely access objects created by less privileged code, by showing the
8 caller only the native version of the objects.
10 Gecko runs JavaScript from a variety of different sources and at a
11 variety of different privilege levels.
13 - The JavaScript code that along with the C++ core, implements the
14 browser itself is called *chrome code* and runs using system
15 privileges. If chrome-privileged code is compromised, the attacker
16 can take over the user's computer.
17 - JavaScript loaded from normal web pages is called *content code*.
18 Because this code is being loaded from arbitrary web pages, it is
19 regarded as untrusted and potentially hostile, both to other websites
21 - As well as these two levels of privilege, chrome code can create
22 sandboxes. The security principal defined for the sandbox determines
23 its privilege level. If an
24 Expanded Principal is used, the sandbox is granted certain privileges
25 over content code and is protected from direct access by content
28 | The security machinery in Gecko ensures that there's asymmetric access
29 between code at different privilege levels: so for example, content
30 code can't access objects created by chrome code, but chrome code can
31 access objects created by content.
32 | However, even the ability to access content objects can be a security
33 risk for chrome code. JavaScript's a highly malleable language.
34 Scripts running in web pages can add extra properties to DOM objects
35 (also known as expando properties)
36 and even redefine standard DOM objects to do something unexpected. If
37 chrome code relies on such modified objects, it can be tricked into
38 doing things it shouldn't.
39 | For example: ``window.confirm()`` is a DOM
40 API that's supposed to ask the user to confirm an action, and return a
41 boolean depending on whether they clicked "OK" or "Cancel". A web page
42 could redefine it to return ``true``:
46 window.confirm = function() {
50 Any privileged code calling this function and expecting its result to
51 represent user confirmation would be deceived. This would be very naive,
52 of course, but there are more subtle ways in which accessing content
53 objects from chrome can cause security problems.
55 | This is the problem that Xray vision is designed to solve. When a
56 script accesses an object using Xray vision it sees only the native
57 version of the object. Any expandos are invisible, and if any
58 properties of the object have been redefined, it sees the original
59 implementation, not the redefined version.
60 | So in the example above, chrome code calling the content's
61 ``window.confirm()`` would get the original version of ``confirm()``,
62 not the redefined version.
66 It's worth emphasizing that even if content tricks chrome into
67 running some unexpected code, that code does not run with chrome
68 privileges. So this is not a straightforward privilege escalation
69 attack, although it might lead to one if the chrome code is
70 sufficiently confused.
72 .. _How_you_get_Xray_vision:
74 How you get Xray vision
75 -----------------------
77 Privileged code automatically gets Xray vision whenever it accesses
78 objects belonging to less-privileged code. So when chrome code accesses
79 content objects, it sees them with Xray vision:
84 var transfer = gBrowser.contentWindow.confirm("Transfer all my money?");
85 // calls the native implementation
89 Note that using window.confirm() would be a terrible way to implement
90 a security policy, and is only shown here to illustrate how Xray
93 .. _Waiving_Xray_vision:
98 | Xray vision is a kind of security heuristic, designed to make most
99 common operations on untrusted objects simple and safe. However, there
100 are some operations for which they are too restrictive: for example,
101 if you need to see expandos on DOM objects. In cases like this you can
102 waive Xray protection, but then you can no longer rely on any
103 properties or functions being, or doing, what you expect. Any of them,
104 even setters and getters, could have been redefined by untrusted code.
105 | To waive Xray vision for an object you can use
106 Components.utils.waiveXrays(object),
107 or use the object's ``wrappedJSObject`` property:
112 var waivedWindow = Components.utils.waiveXrays(gBrowser.contentWindow);
113 var transfer = waivedWindow.confirm("Transfer all my money?");
114 // calls the redefined implementation
119 var waivedWindow = gBrowser.contentWindow.wrappedJSObject;
120 var transfer = waivedWindow.confirm("Transfer all my money?");
121 // calls the redefined implementation
123 Waivers are transitive: so if you waive Xray vision for an object, then
124 you automatically waive it for all the object's properties. For example,
125 ``window.wrappedJSObject.document`` gets you the waived version of
128 To undo the waiver again, call Components.utils.unwaiveXrays(waivedObject):
132 var unwaived = Components.utils.unwaiveXrays(waivedWindow);
133 unwaived.confirm("Transfer all my money?");
134 // calls the native implementation
136 .. _Xrays_for_DOM_objects:
138 Xrays for DOM objects
139 ---------------------
141 The primary use of Xray vision is for DOM objects: that is, the
142 objects that represent parts of the web page.
144 In Gecko, DOM objects have a dual representation: the canonical
145 representation is in C++, and this is reflected into JavaScript for the
146 benefit of JavaScript code. Any modifications to these objects, such as
147 adding expandos or redefining standard properties, stays in the
148 JavaScript reflection and does not affect the C++ representation.
150 The dual representation enables an elegant implementation of Xrays: the
151 Xray just directly accesses the C++ representation of the original
152 object, and doesn't go to the content's JavaScript reflection at all.
153 Instead of filtering out modifications made by content, the Xray
154 short-circuits the content completely.
156 This also makes the semantics of Xrays for DOM objects clear: they are
157 the same as the DOM specification, since that is defined using the
158 `WebIDL <http://www.w3.org/TR/WebIDL/>`__, and the WebIDL also defines
159 the C++ representation.
161 .. _Xrays_for_JavaScript_objects:
163 Xrays for JavaScript objects
164 ----------------------------
166 Until recently, built-in JavaScript objects that are not part of the
168 ``Date``, ``Error``, and ``Object``, did not get Xray vision when
169 accessed by more-privileged code.
171 Most of the time this is not a problem: the main concern Xrays solve is
172 with untrusted web content manipulating objects, and web content is
173 usually working with DOM objects. For example, if content code creates a
174 new ``Date`` object, it will usually be created as a property of a DOM
175 object, and then it will be filtered out by the DOM Xray:
181 // redefine Date.getFullYear()
182 Date.prototype.getFullYear = function() {return 1000};
183 var date = new Date();
189 // contentWindow is an Xray, and date is an expando on contentWindow
190 // so date is filtered out
191 gBrowser.contentWindow.date.getFullYear()
192 // -> TypeError: gBrowser.contentWindow.date is undefined
194 The chrome code will only even see ``date`` if it waives Xrays, and
195 then, because waiving is transitive, it should expect to be vulnerable
202 Components.utils.waiveXrays(gBrowser.contentWindow).date.getFullYear();
205 However, there are some situations in which privileged code will access
206 JavaScript objects that are not themselves DOM objects and are not
207 properties of DOM objects. For example:
209 - the ``detail`` property of a CustomEvent fired by content could be a JavaScript
210 Object or Date as well as a string or a primitive
211 - the return value of ``evalInSandbox()`` and any properties attached to the
212 ``Sandbox`` object may be pure JavaScript objects
214 Also, the WebIDL specifications are starting to use JavaScript types
215 such as ``Date`` and ``Promise``: since WebIDL definition is the basis
216 of DOM Xrays, not having Xrays for these JavaScript types starts to seem
219 So, in Gecko 31 and 32 we've added Xray support for most JavaScript
222 Like DOM objects, most JavaScript built-in objects have an underlying
223 C++ state that is separate from their JavaScript representation, so the
224 Xray implementation can go straight to the C++ state and guarantee that
225 the object will behave as its specification defines:
231 var sandboxScript = 'Date.prototype.getFullYear = function() {return 1000};' +
232 'var date = new Date(); ';
234 var sandbox = Components.utils.Sandbox("https://example.org/");
235 Components.utils.evalInSandbox(sandboxScript, sandbox);
237 // Date objects are Xrayed
238 console.log(sandbox.date.getFullYear());
241 // But you can waive Xray vision
242 console.log(Components.utils.waiveXrays(sandbox.date).getFullYear());
247 To test out examples like this, you can use the Scratchpad in
249 for the code snippet, and the Browser Console to see the expected
252 Because code running in Scratchpad's browser context has chrome
253 privileges, any time you use it to run code, you need to understand
254 exactly what the code is doing. That includes the code samples in
257 .. _Xray_semantics_for_Object_and_Array:
259 Xray semantics for Object and Array
260 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
262 The exceptions are ``Object``
263 and ``Array``: their interesting state is in JavaScript, not C++. This
264 means that the semantics of their Xrays have to be independently
265 defined: they can't simply be defined as "the C++ representation".
267 The aim of Xray vision is to make most common operations simple and
268 safe, avoiding the need to access the underlying object except in more
269 involved cases. So the semantics defined for ``Object`` and ``Array``
270 Xrays aim to make it easy for privileged code to treat untrusted objects
271 like simple dictionaries.
274 of the object are visible in the Xray. If the object has properties
275 which are themselves objects, and these objects are same-origin with the
276 content, then their value properties are visible as well.
278 There are two main sorts of restrictions:
280 - First, the chrome code might expect to rely on the prototype's
281 integrity, so the object's prototype is protected:
283 - the Xray has the standard ``Object`` or ``Array`` prototype,
284 without any modifications that content may have done to that
285 prototype. The Xray always inherits from this standard prototype,
286 even if the underlying instance has a different prototype.
287 - if a script has created a property on an object instance that
288 shadows a property on the prototype, the shadowing property is not
291 - Second, we want to prevent the chrome code from running content code,
292 so functions and accessor properties
293 of the object are not visible in the Xray.
295 These rules are demonstrated in the script below, which evaluates a
296 script in a sandbox, then examines the object attached to the sandbox.
300 To test out examples like this, you can use the Scratchpad in
301 browser context for the code snippet, and the Browser Console
302 to see the expected output.
304 Because code running in Scratchpad's browser context has chrome
305 privileges, any time you use it to run code, you need to understand
306 exactly what the code is doing. That includes the code samples in
313 * redefines Object.prototype.toSource()
314 * creates a Person() constructor that:
315 * defines a value property "firstName" using assignment
316 * defines a value property which shadows "constructor"
317 * defines a value property "address" which is a simple object
318 * defines a function fullName()
319 * using defineProperty, defines a value property on Person "lastName"
320 * using defineProperty, defines an accessor property on Person "middleName",
321 which has some unexpected accessor behavior
324 var sandboxScript = 'Object.prototype.toSource = function() {'+
325 ' return "not what you expected?";' +
327 'function Person() {' +
328 ' this.constructor = "not a constructor";' +
329 ' this.firstName = "Joe";' +
330 ' this.address = {"street" : "Main Street"};' +
331 ' this.fullName = function() {' +
332 ' return this.firstName + " " + this.lastName;'+
335 'var me = new Person();' +
336 'Object.defineProperty(me, "lastName", {' +
337 ' enumerable: true,' +
338 ' configurable: true,' +
342 'Object.defineProperty(me, "middleName", {' +
343 ' enumerable: true,' +
344 ' configurable: true,' +
345 ' get: function() { return "wait, is this really a getter?"; }' +
348 var sandbox = Components.utils.Sandbox("https://example.org/");
349 Components.utils.evalInSandbox(sandboxScript, sandbox);
351 // 1) trying to access properties in the prototype that have been redefined
352 // (non-own properties) will show the original 'native' version
353 // note that functions are not included in the output
354 console.log("1) Property redefined in the prototype:");
355 console.log(sandbox.me.toSource());
356 // -> "({firstName:"Joe", address:{street:"Main Street"}, lastName:"Smith"})"
358 // 2) trying to access properties on the object that shadow properties
359 // on the prototype will show the original 'native' version
360 console.log("2) Property that shadows the prototype:");
361 console.log(sandbox.me.constructor);
364 // 3) value properties defined by assignment to this are visible:
365 console.log("3) Value property defined by assignment to this:");
366 console.log(sandbox.me.firstName);
369 // 4) value properties defined using defineProperty are visible:
370 console.log("4) Value property defined by defineProperty");
371 console.log(sandbox.me.lastName);
374 // 5) accessor properties are not visible
375 console.log("5) Accessor property");
376 console.log(sandbox.me.middleName);
379 // 6) accessing a value property of a value-property object is fine
380 console.log("6) Value property of a value-property object");
381 console.log(sandbox.me.address.street);
384 // 7) functions defined on the sandbox-defined object are not visible in the Xray
385 console.log("7) Call a function defined on the object");
387 console.log(sandbox.me.fullName());
392 // -> TypeError: sandbox.me.fullName is not a function
394 // now with waived Xrays
395 console.log("Now with waived Xrays");
397 console.log("1) Property redefined in the prototype:");
398 console.log(Components.utils.waiveXrays(sandbox.me).toSource());
399 // -> "not what you expected?"
401 console.log("2) Property that shadows the prototype:");
402 console.log(Components.utils.waiveXrays(sandbox.me).constructor);
403 // -> "not a constructor"
405 console.log("3) Accessor property");
406 console.log(Components.utils.waiveXrays(sandbox.me).middleName);
407 // -> "wait, is this really a getter?"
409 console.log("4) Call a function defined on the object");
410 console.log(Components.utils.waiveXrays(sandbox.me).fullName());