xray_vision.rst (16154B)
1 Xray Vision 2 =========== 3 4 .. container:: summary 5 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. 9 10 Gecko runs JavaScript from a variety of different sources and at a 11 variety of different privilege levels. 12 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 20 and to the user. 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 26 code. 27 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``: 43 44 .. code:: JavaScript 45 46 window.confirm = function() { 47 return true; 48 } 49 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. 54 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. 63 64 .. note:: 65 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. 71 72 .. _How_you_get_Xray_vision: 73 74 How you get Xray vision 75 ----------------------- 76 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: 80 81 .. code:: JavaScript 82 83 // chrome code 84 var transfer = gBrowser.contentWindow.confirm("Transfer all my money?"); 85 // calls the native implementation 86 87 .. note:: 88 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 91 vision works. 92 93 .. _Waiving_Xray_vision: 94 95 Waiving Xray vision 96 ------------------- 97 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: 108 109 .. code:: JavaScript 110 111 // chrome code 112 var waivedWindow = Components.utils.waiveXrays(gBrowser.contentWindow); 113 var transfer = waivedWindow.confirm("Transfer all my money?"); 114 // calls the redefined implementation 115 116 .. code:: JavaScript 117 118 // chrome code 119 var waivedWindow = gBrowser.contentWindow.wrappedJSObject; 120 var transfer = waivedWindow.confirm("Transfer all my money?"); 121 // calls the redefined implementation 122 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 126 ``document``. 127 128 To undo the waiver again, call Components.utils.unwaiveXrays(waivedObject): 129 130 .. code:: JavaScript 131 132 var unwaived = Components.utils.unwaiveXrays(waivedWindow); 133 unwaived.confirm("Transfer all my money?"); 134 // calls the native implementation 135 136 .. _Xrays_for_DOM_objects: 137 138 Xrays for DOM objects 139 --------------------- 140 141 The primary use of Xray vision is for DOM objects: that is, the 142 objects that represent parts of the web page. 143 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. 149 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. 155 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. 160 161 .. _Xrays_for_JavaScript_objects: 162 163 Xrays for JavaScript objects 164 ---------------------------- 165 166 Until recently, built-in JavaScript objects that are not part of the 167 DOM, such as 168 ``Date``, ``Error``, and ``Object``, did not get Xray vision when 169 accessed by more-privileged code. 170 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: 176 177 .. code:: JavaScript 178 179 // content code 180 181 // redefine Date.getFullYear() 182 Date.prototype.getFullYear = function() {return 1000}; 183 var date = new Date(); 184 185 .. code:: JavaScript 186 187 // chrome code 188 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 193 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 196 to redefinition: 197 198 .. code:: JavaScript 199 200 // chrome code 201 202 Components.utils.waiveXrays(gBrowser.contentWindow).date.getFullYear(); 203 // -> 1000 204 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: 208 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 213 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 217 arbitrary. 218 219 So, in Gecko 31 and 32 we've added Xray support for most JavaScript 220 built-in objects. 221 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: 226 227 .. code:: JavaScript 228 229 // chrome code 230 231 var sandboxScript = 'Date.prototype.getFullYear = function() {return 1000};' + 232 'var date = new Date(); '; 233 234 var sandbox = Components.utils.Sandbox("https://example.org/"); 235 Components.utils.evalInSandbox(sandboxScript, sandbox); 236 237 // Date objects are Xrayed 238 console.log(sandbox.date.getFullYear()); 239 // -> 2014 240 241 // But you can waive Xray vision 242 console.log(Components.utils.waiveXrays(sandbox.date).getFullYear()); 243 // -> 1000 244 245 .. note:: 246 247 To test out examples like this, you can use the Scratchpad in 248 browser context 249 for the code snippet, and the Browser Console to see the expected 250 output. 251 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 255 this article. 256 257 .. _Xray_semantics_for_Object_and_Array: 258 259 Xray semantics for Object and Array 260 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 261 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". 266 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. 272 273 Any value properties 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. 277 278 There are two main sorts of restrictions: 279 280 - First, the chrome code might expect to rely on the prototype's 281 integrity, so the object's prototype is protected: 282 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 289 visible in the Xray 290 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. 294 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. 297 298 .. note:: 299 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. 303 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 307 this article. 308 309 .. code:: JavaScript 310 311 /* 312 The sandbox script: 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 322 */ 323 324 var sandboxScript = 'Object.prototype.toSource = function() {'+ 325 ' return "not what you expected?";' + 326 '};' + 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;'+ 333 ' };' + 334 '};' + 335 'var me = new Person();' + 336 'Object.defineProperty(me, "lastName", {' + 337 ' enumerable: true,' + 338 ' configurable: true,' + 339 ' writable: true,' + 340 ' value: "Smith"' + 341 '});' + 342 'Object.defineProperty(me, "middleName", {' + 343 ' enumerable: true,' + 344 ' configurable: true,' + 345 ' get: function() { return "wait, is this really a getter?"; }' + 346 '});'; 347 348 var sandbox = Components.utils.Sandbox("https://example.org/"); 349 Components.utils.evalInSandbox(sandboxScript, sandbox); 350 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"})" 357 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); 362 // -> function() 363 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); 367 // -> "Joe" 368 369 // 4) value properties defined using defineProperty are visible: 370 console.log("4) Value property defined by defineProperty"); 371 console.log(sandbox.me.lastName); 372 // -> "Smith" 373 374 // 5) accessor properties are not visible 375 console.log("5) Accessor property"); 376 console.log(sandbox.me.middleName); 377 // -> undefined 378 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); 382 // -> "Main Street" 383 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"); 386 try { 387 console.log(sandbox.me.fullName()); 388 } 389 catch (e) { 390 console.error(e); 391 } 392 // -> TypeError: sandbox.me.fullName is not a function 393 394 // now with waived Xrays 395 console.log("Now with waived Xrays"); 396 397 console.log("1) Property redefined in the prototype:"); 398 console.log(Components.utils.waiveXrays(sandbox.me).toSource()); 399 // -> "not what you expected?" 400 401 console.log("2) Property that shadows the prototype:"); 402 console.log(Components.utils.waiveXrays(sandbox.me).constructor); 403 // -> "not a constructor" 404 405 console.log("3) Accessor property"); 406 console.log(Components.utils.waiveXrays(sandbox.me).middleName); 407 // -> "wait, is this really a getter?" 408 409 console.log("4) Call a function defined on the object"); 410 console.log(Components.utils.waiveXrays(sandbox.me).fullName()); 411 // -> "Joe Smith"