tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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"