Avoid Common Pitfalls in Greasemonkey
by Mark Pilgrim, author of Greasemonkey Hacks
How the History of Greasemonkey Security Affects You Now
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
@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
This leads directly to the first security hole. When Greasemonkey 0.3 inserted a user script into a page, this triggered a
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
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
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_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
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:
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
But wait; it gets worse.