Adblock Plus and (a little) more

Do JavaScript proxies allow undetectable function wrappers? · 2011-04-15 18:13 by Wladimir Palant

I am looking into whether an extension can use JavaScript proxies to do something similar to the built-in pop-up blocker since the built-in pop-up blocker uses a fixed logic that cannot be extended. The goal is to wrap window.open method to verify that any calls are “legit”. Unfortunately, no matter what I try the webpage is able to detect that it is dealing with a proxy and not the real function.

The starting point is the forwarding proxy from MDC. I create a function proxy like this:

  var origFunc = Window.prototype.open;
  Window.prototype.open = Proxy.createFunction(handlerMaker(origFunc),
             function() { return origFunc.apply(this, arguments); },
             function() { return new origFunc(); });

This seems to do fine until you try:

  alert(window.open);

Looks like toString() operates on the data of the proxy directly rather than calling the proxy handler. Instead of displaying something about “native code” it will show the source code of my call trap.

Ok, this can be “cured” by modifying the get trap and making sure that any returned methods are wrapped as well — if in a call to this method this pointer is our proxy they will change it to the wrapped function. Now we have to be careful as to not wrap methods that have been set by the webpage (otherwise the webpage can set a property to a function and then compare it to the original value) but the real problem is this:

  alert(Function.prototype.toString.apply(window.open));

And I don’t think there is any way to prevent this from busting our proxy. So, am I trying to do something that the proxies weren’t meant for or am I simply missing something?

And while we are at it, the webpage can undo the manipulation above trivially:

  delete Window.prototype.open;

It seems that this prototype object is very special, it will recover properties out of nowhere if deleted. Is there any way to make the change stick (without freezing the object)?

Tags:

Comment [5]

  1. Tom Schuster · 2011-04-15 19:31 · #

    This exploits some implementation details, just thought it would be funny to do. You still need to wrap every property you give away. It only makes Function.prototype.toString throw so probably not worth the effort or won’t work in the future.

    window_open = Window.prototype.open;

    var inception = Proxy.createFunction({}, function () { window_open.apply(this, arguments);
    });

    var proxy = Proxy.createFunction(handlerMaker(window_open), inception);

    Object.defineProperty(Window.prototype, ‘open’, {configurable: false, value: proxy}); /* configurable false prevents deletion */

  2. Mook · 2011-04-16 08:28 · #

    Nope, pretty sure Proxies are useless; Array.prototype.toString has the same problem.

    Also not working: (typeof proxy), (proxy instanceof Function). Those can be harder to wrap…

    I also don’t understand what proxies are meant for, since all the good things, it can’t do; combined with the incredible amount of setup required (rather than, say, p = Proxy.create(o, {/* opt in only to things you need*/}) or something…)

    Reply from Wladimir Palant:

    typeof proxy and proxy instanceof Function work correctly for me, at least when using correct code (I fixed the example code above now).

  3. Colby Russell · 2011-04-16 09:13 · #

    “Window.prototype.open = handlerMaker(origFunc,”

    There’s your problem. handlerMaker gives back a handler that implements all the traps. The handler should be given to Proxy.create/Proxy.createFunction (you want the latter one here). What you get back from that is what you want to override the baked-in |open| with.

    Reply from Wladimir Palant:

    Thanks, fixed the example code here. But I was testing with the correct code :)

  4. Colby Russell · 2011-04-16 09:24 · #

    Mook, proxies are gonna change the world. Though, possibly for the worst.

    (Grappling with the layers involved can get crazy, especially when you start talking about proxies for your proxies’ handlers.)

    I haven’t actually read the MDC page on proxies, except to check out handlerMaker referenced in the post. It may be more accurate with regard the current implementation in Firefox 4, but Tom Van Cutsem’s tutorial <http://soft.vub.ac.be/~tvcutsem/proxies/> lays it all out pretty well. I’d suggest starting there, then cross-referencing what MDC has to say for getting the details right. Or not. The page there might not actually reflect the current implementation very well at all.)

  5. Alex Vincent · 2011-04-16 21:22 · #

    I would wager right now the answer is “no”. The function wrapper might work, but if that function returns a nsISupports object, then you’re pretty much dead.

    The problem is proxiedObject instanceof Components.interfaces.nsISupports always returns false.

    I’ve been playing with proxies for a couple months now, and I should point out you don’t want a proxy so much as a whole proxy membrane. This basically means that once a script is handed a proxy from the membrane, no matter what the script does, it can’t get an object outside the membrane. Right now it’s harder to do than it should be, but Harmony’s Weak Maps will solve a big piece of it.

    I’ve recently implemented a proxy membrane for my Verbosio project. Feel free to contact me, or Dave Herman, or Andreas Gal if you have further thoughts.

Commenting is closed for this article.