Simon Willison’s Weblog

Understanding the Greasemonkey vulnerability

If you have any version of Greasemonkey installed prior to 0.3.5, which was released a few hours ago, or if you are running any of the 0.4 alphas, you need to go and upgrade right now. All versions of Greasemonkey aside from 0.3.5 contain a nasty security hole, which could enable malicious web sites to read any file from your hard drive without you knowing.

Unfortunately, 0.3.5 disables all of the GM_ API functions, without which many of the more interesting user scripts out there simple won’t work. This is a temporary measure—the GM_ functions should return in a later release, once the security problem with them has been resolved.

I’m going to explain how the vulnerability works, because it illustrates a number of interesting concepts in both web application security and JavaScript.

Same-origin policy

JavaScript has always enforced a same-origin policy for scripts loaded over the internet. This originally applied to scripting between frames (and iframes): a script loaded from a certain domain is only allowed to access the DOM of other pages loaded from that same domain. The same restriction has been extended to XMLHttpRequest—you are only allowed to make an XMLHttpRequest call back to the domain from which the script was originally loaded.

This policy exists to prevent cross-domain attacks. Say for example you work for a company with an intranet hidden behind the firewall, full of interesting proprietary information. Without the same-origin policy, malicious sites that you visit on the public internet would be able to read information from your intranet, using your browser as the middle-man.

GM_xmlhttpRequest

The GM_xmlhttpRequest API function does not have this restriction—it can load data from any domain. This enables a whole host of interesting user scripts—the most famous of which is probably Book Burro, which shows comparison prices from different online stores for the item you are currently looking at on Amazon, Barnes and Noble and more.

Malicious user scripts could use this feature to steal information from your private intranet, but malicious user scripts could also do all manner of other nasty things—stealing your Hotmail password for example. This is why you should never install a user script from an untrusted source without first reviewing the code.

Restricting API functions to user scripts only

To keep things safe then, it is essential that the GM_ family of API functions can only ever be used by user scripts, not by code running on pages that you have visited. By installing a user script you have declared it trustworthy—but visiting a web page does not carry that contract.

The way the flawed versions of Greasemonkey do that now is simple: the GM_ functions are added to the JavaScript global object (which is the window object), the user scripts for the page are “injected” using dynamically created <script> elements, they run, then the GM_ functions are removed from the global object to prevent scripts on the page from accessing them. This works because Greasemonkey injection and execution happens just before the onload event is fired—which is when most well behaved scripts kick in.

Object.watch()

Here’s the clever part: JavaScript 1.5 defines a method of the Object class (which is inherited by all other JavaScript objects) called watch. Watch is extremely powerful: it lets you register some code to be executed when a property on some other object is assigned. This is the key to the Greasemonkey vulnerability—by watching the window object for the point at which Greasemonkey adds the API functions, a malicious script can use those functions at the moment they are attached.

The file:// protocol

Here’s the final piece of the puzzle: the file:// protocol in Firefox allows you to view files and directory listings in your browser. Unfortunately, it also allows the GM_xmlhttpRequest function to do the same. It’s not at all hard for a malicious script to use the function to load in files at a known location—or even load in directory listings (as HTML), parse them and use them to find all kinds of things scattered around your hard drive.

Solving the problem

The principle problem then is the requirement for “safe” Greasemonkey API functions—that is, functions that can be used by the user scripts but not by code running on a website. Aaron is looking in to this right now—it looks like the solution will require a minor change to be made to many existing scripts, but the trade-off in terms of security is more than worth it. The GM_xmlhttpRequest function will also be modified to disallow file:// URLs.

Until then, 0.3.5 is the only safe version of Greasemonkey.

This is Understanding the Greasemonkey vulnerability by Simon Willison, posted on 20th July 2005.

Tagged , ,

Next: Exciting developments with Django

Previous: Introducing Django

Previously hosted at http://simon.incutio.com/archive/2005/07/20/vulnerability