advertisement

Print

Avoid Common Pitfalls in Greasemonkey
How the History of Greasemonkey Security Affects You Now

by Mark Pilgrim, author of Greasemonkey Hacks
11/11/2005

Once upon a time, there was a security hole. (This is not your standard fairy tale. Stay with me.) Greasemonkey's architecture has changed substantially since it was first written. Version 0.3, the first version to gain wide popularity, had a fundamental security flaw: it trusted the remote page too much when it injected and executed user scripts.

Back in those days, Greasemonkey's injection mechanism was simple, elegant--and wrong. It initialized a set of API functions as properties of the global window object, so that user scripts could call them. Then, it determined which user scripts ought to execute on the current page based on the @include and @exclude parameters. It loaded the source code of each user script, created a <script> element, assigned the source code of the user script to the contents of the <script> element, and inserted the element into the page. Once all of the user scripts finished, Greasemonkey cleaned up the page by removing the <script> elements it had inserted and removing the global properties it had added.

Simple and elegant, to be sure; so why was it wrong?

Security Hole #1: Source Code Leakage

The answer lies in the largely untapped power of the JavaScript language and the Document Object Model (DOM). JavaScript running in a browser is not simply a scripting language. The browser sets up a complex object hierarchy for scripts to manipulate the web page, and a complex event model to notify scripts when things happen.

This leads directly to the first security hole. When Greasemonkey 0.3 inserted a user script into a page, this triggered a DOMNodeInserted event, which the remote page could intercept. Consider a web page with the following JavaScript code. Keep in mind, this is not a user script; this is just regular JavaScript code that is part of the web page in which user scripts are executing.

<script type="text/javascript">
_scripts = [];
_c = document.getElementsByTagName("script").length;
function trapInsertScript(event) {
    var doc = event.currentTarget;
    var arScripts = doc.getElementsByTagName("script");
    if (arScripts.length > _numPreviousScripts) {
        _scripts.push(arScripts[_c++].innerHTML);
    }
}
document.addEventListener("DOMNodeInserted", trapInsertScript, true);
</script>

Whenever Greasemonkey 0.3 injected a user script into this page (by adding a <script> element), Firefox called the trapInsertScript function, which allowed the remote page to store a copy of the entire source code of the user script that had just been injected. Even though Greasemonkey removed the <script> element immediately, the damage had already been done. The remote page could get a complete copy of every user script that executed on the page, and do whatever it wanted with that information.

Clearly, this is undesirable. But it gets worse.

Security Hole #2: API Leakage

The most powerful feature of Greasemonkey is not that it allows you to inject your own scripts into third-party web pages. User scripts can actually do things that regular unprivileged JavaScript cannot do, because Greasemonkey provides a set of API functions specifically for user scripts:

  • GM_setValue: Store a script-specific value in the Firefox preferences database. You can see these stored values by navigating to about:config and filtering on greasemonkey.

  • GM_getValue: Retrieve a script-specific value from the Firefox preferences database. User scripts can only access values that they stored; they cannot access values stored by other user scripts, other browser extensions, or Firefox itself.

  • GM_log: Log a message to the JavaScript Console.

  • GM_registerMenuCommand: Add a menu item to the User Script Commands menu, under the Tools menu.

  • GM_xmlhttpRequest: Get or post an HTTP request with any URL, any headers, and any data.

This last API function is obviously the most powerful. It is also the most useful, because it allows user scripts to integrate data from different sites. Greasemonkey Hacks devotes Chapter 11 to GM_xmlhttpRequest.

JavaScript code that comes with a regular web page cannot do this. There is an XMLHttpRequest object that has some of the same capabilities, but for security reasons, Firefox intentionally restricts it to communicating with other pages on the same website. Greasemonkey's GM_xmlhttpRequest function loosens this restriction and allows user scripts to communicate with any website, anywhere, anytime.

All of this brings us to the second security hole. Greasemonkey 0.3 allowed remote page scripts not only to "steal" the source code of user scripts, but to steal access to Greasemonkey's API functions:

<script type="text/javascript">
_GM_xmlhttpRequest = null;
function trapGM(prop, oldVal, newVal) {
    _GM_xmlhttpRequest = window.GM_xmlhttpRequest;
    return newVal;
}
window.watch("GM_log", trapGM);
</script>

Using the watch method, available on every JavaScript object, the web page would wait for Greasemonkey 0.3 to add the GM_log function to the window object. As long as at least one user script executed on the page, this would always happen, immediately before Greasemonkey inserted the <script> element that ran the user script. When Greasemonkey assigned the window.GM_log property, Firefox would call the trapGM function set up by the remote page, which could steal a reference to window.GM_xmlhttpRequest and store it for later use.

The user script would execute as usual, and Greasemonkey would clean up after itself by removing the API functions from the window object. But the damage had already been done. The remote page still retained a reference to the GM_xmlhttpRequest function, and it could use this function reference to do things that ordinary JavaScript code is not supposed to be able to do.

Security experts call this a privilege escalation attack. In effect, Greasemonkey 0.3 circumvented all of the careful planning that went into sandboxing unprivileged JavaScript code, and allowed unprivileged code to gain access to privileged functions.

But wait; it gets worse.

Greasemonkey Hacks

Related Reading

Greasemonkey Hacks
Tips & Tools for Remixing the Web with Firefox
By Mark Pilgrim

Pages: 1, 2, 3, 4, 5

Next Pagearrow