Safely accessing content DOM from chrome

  • Revision slug: Safely_accessing_content_DOM_from_chrome
  • Revision title: Safely accessing content DOM from chrome
  • Revision id: 98896
  • Created:
  • Creator: Bzbarsky
  • Is current revision? No
  • Comment /* Direct access */

Revision Content

Introduction

Application and extensions that have Javascript code using DOM interfaces on untrusted (Web) content need to be careful that the information that they use is really coming from the DOM API and not from JavaScript properties, getter functions, and setter functions defined by a malicious page. Firefox 1.0.3 and Mozilla 1.7.7 make it harder for Web pages to trick XUL applications and extensions by ensuring that when chrome Javascript accesses a DOM property or method on an object, it will get the DOM property or method rather than the Web page's override. Firefox 1.1 has a more general solution, but extensions must currently opt in to gain the security benefits.

There are only two "correct" ways for chrome code to access the content's DOM: direct access, and explicit use of XPCNativeWrapper. In particular, the commonly used __proto__ trick is not secure in any version (see "Examples of what NOT to do" below).

The following table summarizes the security properties of the two "correct" methods:

Direct access Explicit XPCNativeWrapper
Firefox 1.0.2 and below insecure secure
Firefox 1.0.3 to 1.0.4 secure when property guaranteed to exist secure
Firefox 1.1 secure with xpcnativewrappers=yes secure

Direct access

Scripts designed to run only in Firefox 1.0.3 and later 1.0.x versions or that use xpcnativewrappers=yes in Firefox 1.1 or later may simply call:

return contentWindow.document.title == contentWindow.getSelection();

Direct access is secure in Firefox 1.0.3 (and later 1.0.x versions) as long as the object is guaranteed to have the property or method that is accessed through its IDL declaration. For example, foo.nodeType is secure as long as you are sure foo is a Node, and foo.getSelection() is secure as long as you are sure foo is a window. Getting this right can be tricky -- for example, nsIDOMNSHTMLDocument has an open() method, but nsIDOMXULDocument does not, so using document.open() is NOT safe in Firefox 1.0.3, since document might be a XUL document. In such cases, you can use the instanceOf operator to determine whether you have an object that supports a given IDL interface (nsIDOMNSHTMLDocument in this case).

In Firefox 1.1, direct access is always secure if your extension uses the new xpcnativewrappers=yes flag in its manifest because then use of XPCNativeWrapper is implicit.

Explicit use of XPCNativeWrapper

var winWrapper = new XPCNativeWrapper(contentWindow,
                                      'document', 'getSelection()');
var docWrapper = new XPCNativeWrapper(winWrapper.document, 'title');
return docWrapper.title == winWrapper.getSelection();

Note that this example uses two wrappers to get window.document.title, one wrapper for getting the document property from the window, and one wrapper for getting the title property from the document.

Use of XPCNativeWrapper is secure in all versions of Firefox, but it makes code harder to read and you have to be careful to wrap every DOM object.

For more information on this syntax, see the entry for XPCNativeWrapper at the MozillaZine KnowledgeBase.

About XPCNativeWrapper

XPCNativeWrapper is a way to wrap up an object so that it's safe to access from privileged code.

There are two ways to use XPCNativeWrapper. The old way to use it explicitly. The new way, xpcnativewrappers=yes, is only available starting with Firefox 1.1.

What breaks when you use XPCNativeWrapper (explicitly in any version, or with xpcnativewrappers=yes in Firefox 1.1)?

Examples of what NOT to do

BAD in Firefox 1.0.2 and below, since script can override nodeType getter:

return targetNode.nodeType == 1;

BAD in Firefox 1.0.2 and below, since script can override getSelection:

return contentWindow.getSelection();

BAD in all versions. Some developers have, in the past, used this ill-advised trick. Script can override getSelection in older versions and this doesn't work at all in Firefox 1.0.3 and Mozilla 1.7.7:

return contentWindow.__proto__.getSelection.call(contentWindow);

BAD in Firefox 1.0.2 and below, since script can override inner getter even though outer one is safe:

var winWrapper = new XPCNativeWrapper(contentWindow, 'document');
// getting contentWindow.document is now safe, but getting .title off
// the returned document is still not safe.
return winWrapper.document.title;

BAD in versions before Firefox 1.1, since script can set document.open for non-HTML documents, which don't have a DOM document.open:

  return contentWindow.document.open();

Revision Source

<h3 name="Introduction"> Introduction </h3>
<p>Application and extensions that have Javascript code using DOM
interfaces on untrusted (Web) content need to be careful that the
information that they use is really coming from the DOM API and not from
JavaScript properties, getter functions, and setter functions defined by a malicious page.  Firefox 1.0.3 and Mozilla
1.7.7 make it harder for Web pages to trick XUL applications and
extensions by ensuring that when chrome Javascript accesses
a DOM property or method on an object, it will get the DOM
property or method rather than the Web page's override.  Firefox 1.1 has a more general solution, but extensions must currently opt in to gain the security benefits.
</p><p>There are only two "correct" ways for chrome code to access the content's
DOM: direct access, and explicit use of XPCNativeWrapper.  In particular, the commonly used <code>__proto__</code> trick is not secure in any version (see "Examples of what NOT to do" below).
</p><p>The following table summarizes the security properties of the two "correct" methods:
</p>
<table border="1">
<tbody><tr>
<th>
</th><th> Direct access
</th><th> Explicit XPCNativeWrapper
</th></tr>
<tr>
<th> Firefox 1.0.2 and below
</th><td> insecure
</td><td> secure
</td></tr>
<tr>
<th> Firefox 1.0.3 to 1.0.4
</th><td> secure when property guaranteed to exist
</td><td> secure
</td></tr>
<tr>
<th> Firefox 1.1
</th><td> secure with <code>xpcnativewrappers=yes</code>
</td><td> secure
</td></tr></tbody></table>
<h3 name="Direct_access"> Direct access </h3>
<p>Scripts designed to run only in Firefox 1.0.3 and later 1.0.x versions or that use <code>xpcnativewrappers=yes</code> in Firefox 1.1 or later may simply call:
</p>
<pre>return contentWindow.document.title == contentWindow.getSelection();
</pre>
<p>Direct access is secure in Firefox 1.0.3 (and later 1.0.x versions) as long as the object is guaranteed to have the property or method that is accessed through its IDL declaration.  For example, foo.nodeType is secure as long as you are sure foo is a Node, and foo.getSelection() is secure as long as you are sure foo is a window.  Getting this right can be tricky -- for example, nsIDOMNSHTMLDocument has an open() method, but nsIDOMXULDocument does not, so using document.open() is NOT safe in Firefox 1.0.3, since <code>document</code> might be a XUL document.  In such cases, you can use the instanceOf operator to determine whether you have an object that supports a given IDL interface (nsIDOMNSHTMLDocument in this case).
</p><p>In Firefox 1.1, direct access is always secure if your extension uses the new <code>xpcnativewrappers=yes</code> flag in <a href="en/Chrome_Registration">its manifest</a> because then use of <a href="#About_XPCNativeWrapper">XPCNativeWrapper</a> is implicit.
</p>
<h3 name="Explicit_use_of_XPCNativeWrapper"> Explicit use of <a href="#About_XPCNativeWrapper">XPCNativeWrapper</a> </h3>
<pre>var winWrapper = new XPCNativeWrapper(contentWindow,
                                      'document', 'getSelection()');
var docWrapper = new XPCNativeWrapper(winWrapper.document, 'title');
return docWrapper.title == winWrapper.getSelection();
</pre>
<p>Note that this example uses <em>two</em> wrappers to get window.document.title, one wrapper for getting the document property from the window, and one wrapper for getting the title property from the document.
</p><p>Use of XPCNativeWrapper is secure in all versions of Firefox, but it makes code harder to read and you have to be careful to wrap every DOM object.
</p><p>For more information on this syntax, see <a class="external" href="http://kb.mozillazine.org/XPCNativeWrapper">the entry for <code>XPCNativeWrapper</code> at the MozillaZine KnowledgeBase</a>.
</p>
<h3 name="About_XPCNativeWrapper"> About XPCNativeWrapper </h3>
<p><code><a href="en/XPCNativeWrapper">XPCNativeWrapper</a></code> is a way to wrap up an object so that it's safe to access from privileged code.
</p><p>There are two ways to use <code>XPCNativeWrapper</code>.  The old way to use it explicitly.  The <a href="en/XPCNativeWrapper">new way</a>, xpcnativewrappers=yes, is only available starting with Firefox 1.1.
</p><p>What breaks when you use XPCNativeWrapper (explicitly in any version, or with xpcnativewrappers=yes in Firefox 1.1)?
</p>
<h3 name="Examples_of_what_NOT_to_do"> Examples of what NOT to do </h3>
<p>BAD in Firefox 1.0.2 and below, since script can override <code>nodeType</code> getter:
</p>
<pre>return targetNode.nodeType == 1;
</pre>
<p>BAD in Firefox 1.0.2 and below, since script can override <code>getSelection</code>:
</p>
<pre>return contentWindow.getSelection();
</pre>
<p>BAD in <em>all</em> versions. Some developers have, in the past, used this ill-advised trick. Script can override <code>getSelection</code> in older versions <em>and</em> this doesn't work at all in Firefox 1.0.3 and Mozilla 1.7.7:
</p>
<pre>return contentWindow.__proto__.getSelection.call(contentWindow);
</pre>
<p>BAD in Firefox 1.0.2 and below, since script can override inner getter even though outer one is safe:
</p>
<pre>var winWrapper = new XPCNativeWrapper(contentWindow, 'document');
// getting contentWindow.document is now safe, but getting .title off
// the returned document is still not safe.
return winWrapper.document.title;
</pre>
<p>BAD in versions before Firefox 1.1, since script can set document.open for non-HTML documents, which don't have a DOM document.open:
</p>
<pre>  return contentWindow.document.open();
</pre>
Revert to this revision