Why is unsafeWindow unsafe?

943 views
Skip to first unread message

arantius

unread,
Jan 11, 2008, 11:52:06 PM1/11/08
to greasemonkey-dev
I'm researching and experimenting with user script related stuff,
outside the scope of Greasemonkey, while on my gm-dev hiatus. One of
the questions I've wanted a good answer to for quite a while, but
never had a good one was, simply: Why is unsafeWindow unsafe? People,
including me have asked, [1], [2], more?.

Not is it; I trust that it is. I just like to know why, to be
informed. And there hasn't been, or I've never seen, an answer.
Well, I've managed to answer it myself. I tried out a few things and
found out how to exploit it. So the answer to why is:

Accessing any property of unsafeWindow AT ALL will completely expose
all of the privileged GM_ functions to the content page.

Proof of concept:

http://arantius.info/gm/unsafeWindow/005.php

The basic exploit is:

<script type="text/javascript">
window.__defineGetter__('foo', trapFoo);
function trapFoo() {
var gmScope=trapFoo.caller.__parent__;

//gmScope.GM_*(); works here
}
</script>

[1] http://groups.google.com/group/greasemonkey-dev/browse_thread/thread/d83c761d64d9437b/
[2] http://groups.google.com/group/greasemonkey-dev/browse_thread/thread/5681bece2eb7969c/

esquifit

unread,
Jan 12, 2008, 7:35:19 AM1/12/08
to greasemo...@googlegroups.com
+2
Very good! I had already shown, also based on a slight modification
of your original test, that content could expose the *text* of the
userscript accessing unsafe members; in particular making them
inappropriate for storing passwords an other sensible information.

But this one is the definitive proof of concept. I think you should
publish this in the wiki asap and make the folks at greasemonkey-users
and possible at us.o aware of this.

Specially the problem with unauthorized cross-site requests should be
emphasised, since cookies are automatically sent with them; this
means that if you had just visited your online bank and did not remove
cookies after that (even if you have closed all your tabs) your bank
account is open for the whole world. This, combined with the well
known css-hack for detecting whether you have recently visited some
among a number of given sites (for example popular online banks,
amazon, ebay, etc) makes using unsafeWindow in the wild suicidal.

RodMcguire

unread,
Jan 12, 2008, 9:08:02 AM1/12/08
to greasemonkey-dev
That hack might be used in GreaseMonkey itself to give user scripts
access to their global object, which as of now they can't see.

http://wiki.greasespot.net/Global_object

Nikolas Coukouma

unread,
Jan 12, 2008, 4:03:27 PM1/12/08
to greasemo...@googlegroups.com
I read that page, ran some tests, and I still don't understand. The
problem I'm having with Venkman seems to have something to do with the
way evalInSandbox works; the errors and exceptions aren't noticed. The
scope stuff makes no sense at all to me, since the following tests all
seem to work as I'd expect:

x = 3

// false
alert(this === window);

// 3, 3, 3, , , 3
alert("default: " + [x, this.x, this["x"], window.x, window["x"],
eval("x")].join(", "));

(function() {
// false
alert(this === window);

// 3, 3, 3, , , 3
alert("default: " + [x, this.x, this["x"], window.x, window["x"],
eval("x")].join(", "));
}).call();

(function() {
// false
alert(this === window);

// 3, 3, 3, , , 3
alert("default: " + [x, this.x, this["x"], window.x, window["x"],
eval("x")].join(", "));
}).call(arguments.callee.__parent__);

Note that call() with a null or undefined argument sets "this" to the
global object. Also, you *shouldn't* be able to add or override
properties on a wrapped object; that's the entire point of the wrapper.

So ... what is does this hack fix?

I can at least understand some confusion about "this" versus "window",
since the test this === window is true on a normal page. The magic of
sandboxes and wrappers makes things significantly more complicated ...

-Nikolas

signature.asc

Aaron Boodman

unread,
Jan 12, 2008, 4:06:53 PM1/12/08
to greasemo...@googlegroups.com
Ouch, nice find.

I could not think of any fix for this that is backwards compatible. As
a quick fix, the only thing I could come up with is a confirmation
dialog before any cross-domain XHR.

I started hacking this up. It's not quite done yet, but I wanted to
get people's thoughts. I know dialogs are never ideal. After this is
pushed out we can think about better long-term options.

changeset: http://greasemonkey.devjavu.com/changeset/594
XPI: http://www.youngpup.net/z_dropbox/greasemonkey-0.7.xhridea.xpi

I also attached a pic of the new dialog to this mail.

- a

dialog.jpg

Aaron Boodman

unread,
Jan 12, 2008, 4:10:42 PM1/12/08
to greasemo...@googlegroups.com
Oh, and in the pic, obviously you should mentally substitute a real
domain name for "file://". That was just because I was testing from a
file:// URL.

- a

Johan Sundström

unread,
Jan 13, 2008, 3:27:30 AM1/13/08
to greasemo...@googlegroups.com
On Jan 12, 2008 10:06 PM, Aaron Boodman <bo...@youngpup.net> wrote:
> Ouch, nice find.
>
> I could not think of any fix for this that is backwards compatible.

Out of curiosity, which incompatible fixes did you consider? With a
bit of luck, with an evolved community-infused-ideas take on one of
them, we might come up with some solution which is only bad on the
approximate level of the badness induced by going from
old-GM-disastrous to XPCNativeWrapper-bad.

(Got to love my optimism, eh? ;-)

So, brainstorming-think: backwards-incompatible fixes are okay, until
we decide on an action approach, where we trim down our options to
least-bad alternative, padded up with as much backwards-compat as we
can. Venting ideas and half-ideas good. My unresearched dreams and
hopes later down this mail might be around or just past the boundary
where the ideas are just noise, but we probably won't be drowned in
that kind of thing anyway, so fire away if you have anything remotely
useful, I'd say.

> As a quick fix, the only thing I could come up with is a confirmation
> dialog before any cross-domain XHR.
>
> I started hacking this up. It's not quite done yet, but I wanted to
> get people's thoughts. I know dialogs are never ideal. After this is
> pushed out we can think about better long-term options.

-1. This is a Vista:esque dialog, insofar as it provides too little
information to support the user's answer to the question, which will
teach users to learn a click-through behaviour for all the typical
false alarms it will yield, followed by a "well, you brought it down
on yourself; GM *did* ask whether you wanted to empty your bank
account, so it's really your own fault".

As an absolute minimum, the dialog should postulate, very clearly,
*which script* is doing the request. (That's doable by offering each
script a unique GM_xmlHttpRequest bound to invoke the dialog in its
own name.)

Further on, the same origin policy is mis-applied, as the user script
circumstances don't map to those where the (already bad, though
convention bound) same domain policy jail, which kind-of-works in the
web jail metaphor which to a much lesser extent applies to GM scripts.
I'd argue that the "same domain" compared to which *would* make a GM
script apply the same model would be the domain the script was *loaded
from*. Especially as a script missing any @include header applies to
*. Which means that as long as you access your bank, with a @include *
malicious script, the dialog wouldn't even show, and the ideal user,
having been taught that there's a dialog to protect her from xhr
theft, won't see one. For the typical case, that would bring down the
problem to "exploits towards userscripts.org", while resulting in way
more annoying dialogs teaching users the dialog asks a question they
probably considered themselves to have answered already on install
time.

I know, this is not exceptionally constructive criticism, in that I
don't have a patent solution that is Good to offer instead of the
dialog, but, by my perceptions, the problem we should focus on is
breaking the call chain that exposes either or both of rapFoo.caller
and rapFoo.caller.__parent__, or figure out ways of architecting in
tainting code which would introduce a privilege boundary firewall from
page context code reaching for GM context code. (And yes -- without
any idea of how that might be done, that qualifies as a non-tip. :-/)

I'm very much in favour of beating the drums about unsafeWindow based
scripts being worth big "Danger, Will Robinson!" warnings on us.o and
the like, and that is as true now as it's been all along, plus the
added community awareness of exactly how it's exploitable. But I don't
feel that the dialog is a net improvement on the problem. (Not that
I'll be all sour grapes if 0.8 gets one; I just want to voice my
position.)

--
/ Johan Sundström, http://ecmanaut.blogspot.com/

Gareth Andrew

unread,
Jan 13, 2008, 5:32:10 AM1/13/08
to greasemo...@googlegroups.com

I haven't looked into this in any detail, but I can't reproduce the
exploit on Firefox 3b2. Anybody else tried? Maybe there's a glimmer of
hope?

Gareth.

esquifit

unread,
Jan 13, 2008, 6:09:54 AM1/13/08
to greasemo...@googlegroups.com
On Jan 13, 2008 11:32 AM, Gareth Andrew <ginger...@gmail.com> wrote:
>
>
> I haven't looked into this in any detail, but I can't reproduce the
> exploit on Firefox 3b2. Anybody else tried? Maybe there's a glimmer of
> hope?

You are right, I cannot reproduce it on 3b2 either.

Gareth Andrew

unread,
Jan 13, 2008, 6:44:14 AM1/13/08
to greasemo...@googlegroups.com
I think (though I still haven't done any testing) this was fixed with
the new XPCSafeJSObjectWrapper [1][2] introduced in Firefox 3. That
still leaves us with a large security hole until we stop supporting
Firefox 2 and below, or until the fix gets applied in a Firefox 2 release.


[1] - https://bugzilla.mozilla.org/show_bug.cgi?id=355766
[2] -
http://developer.mozilla.org/en/docs/XPConnect_wrappers#XPCSafeJSObjectWrapper

Aaron Boodman

unread,
Jan 13, 2008, 2:05:31 PM1/13/08
to greasemo...@googlegroups.com
On Jan 13, 2008 12:27 AM, Johan Sundström <oya...@gmail.com> wrote:
> Out of curiosity, which incompatible fixes did you consider? With a
> bit of luck, with an evolved community-infused-ideas take on one of
> them, we might come up with some solution which is only bad on the
> approximate level of the badness induced by going from
> old-GM-disastrous to XPCNativeWrapper-bad.

The first thing I considered was checking the caller chain for
__parent__ == unsafeWindow as you suggest below. I suspected that
would break some scripts, so tried to find something that would not.

Another idea is to change GM_xhr to not send cookies for x-domain.
This would obviously break some scripts that rely on you being logged
into various services to interact with them. I looked into using XHR
without cookies once before for another project and couldn't figure it
out, so I don't know how viable that option is.

BTW, I finished the dialog last night and tested it on some us.o
scripts, and it is not that great. Lots of scripts make GM_xhr
requests to their homepage on page load for autoupdate. This is
completely unrelated to the page that you're viewing, so it's very
confusing to get the dialog.

So I think I'm going to try the call-chain checking thing next. It
might catch too many scripts, but it only needs to affect cross-domain
requests, and only FF2. I think you could also narrow it further to
GM_xhr requests after an unsafeWindow access.

> So, brainstorming-think: backwards-incompatible fixes are okay, until
> we decide on an action approach, where we trim down our options to
> least-bad alternative, padded up with as much backwards-compat as we
> can. Venting ideas and half-ideas good. My unresearched dreams and
> hopes later down this mail might be around or just past the boundary
> where the ideas are just noise, but we probably won't be drowned in
> that kind of thing anyway, so fire away if you have anything remotely
> useful, I'd say.

Definitely, sorry if I was brisk. I just want to get some fix in place quickly.

> > As a quick fix, the only thing I could come up with is a confirmation
> > dialog before any cross-domain XHR.
> >
> > I started hacking this up. It's not quite done yet, but I wanted to
> > get people's thoughts. I know dialogs are never ideal. After this is
> > pushed out we can think about better long-term options.
>
> -1. This is a Vista:esque dialog, insofar as it provides too little
> information to support the user's answer to the question, which will
> teach users to learn a click-through behaviour for all the typical
> false alarms it will yield, followed by a "well, you brought it down
> on yourself; GM *did* ask whether you wanted to empty your bank
> account, so it's really your own fault".
>
> As an absolute minimum, the dialog should postulate, very clearly,
> *which script* is doing the request. (That's doable by offering each
> script a unique GM_xmlHttpRequest bound to invoke the dialog in its
> own name.)

Unfortunately, that isn't possible. All scripts play together in
unsafeWindow, the page could grab any one of their GM_xhr instances.

> Further on, the same origin policy is mis-applied, as the user script
> circumstances don't map to those where the (already bad, though
> convention bound) same domain policy jail, which kind-of-works in the
> web jail metaphor which to a much lesser extent applies to GM scripts.
> I'd argue that the "same domain" compared to which *would* make a GM
> script apply the same model would be the domain the script was *loaded
> from*. Especially as a script missing any @include header applies to
> *. Which means that as long as you access your bank, with a @include *
> malicious script, the dialog wouldn't even show, and the ideal user,
> having been taught that there's a dialog to protect her from xhr
> theft, won't see one. For the typical case, that would bring down the
> problem to "exploits towards userscripts.org", while resulting in way
> more annoying dialogs teaching users the dialog asks a question they
> probably considered themselves to have answered already on install
> time.

It seems like @include * (or other messed up @includes) is a separate
issue... We should separately teach user script developers to be
careful with @include, and/or develop a new syntax.

> I know, this is not exceptionally constructive criticism, in that I
> don't have a patent solution that is Good to offer instead of the
> dialog, but, by my perceptions, the problem we should focus on is
> breaking the call chain that exposes either or both of rapFoo.caller
> and rapFoo.caller.__parent__, or figure out ways of architecting in
> tainting code which would introduce a privilege boundary firewall from
> page context code reaching for GM context code. (And yes -- without
> any idea of how that might be done, that qualifies as a non-tip. :-/)

Nah, it's good. I'll check this one next.

> I'm very much in favour of beating the drums about unsafeWindow based
> scripts being worth big "Danger, Will Robinson!" warnings on us.o and
> the like, and that is as true now as it's been all along, plus the
> added community awareness of exactly how it's exploitable. But I don't
> feel that the dialog is a net improvement on the problem. (Not that
> I'll be all sour grapes if 0.8 gets one; I just want to voice my
> position.)

Thanks, I appreciate it.

If anyone wants to help with coding, there is one thing that could be
done in parallel for someone who has a mac handy.

It would be nice if this fix also enabled FF3 support so that users
who have broken scripts have a workaround: upgrade to FF3b2. However,
openInEditor() is broken in FF3 on the 0.7 branch. The latest version
of utils.js on trunk fixes it for Windows, but it breaks for mac. I
think there are two solutions:

* openInEditor() references an interface called nsILocalFileMac. I
think this interface is actually called something weird in FF3b2 like
nsILocalFileMac_GECKO_18_BRANCH or something like that. Changing to
that might fix it.

* Test for mac some other way and then use the old code for mac and
the new code for windows/linux (the old code works on mac in FF3).

If nobody has time, nbd, I can do this myself after.

I'll be in #greasemonkey on freenode if anyone's around and wants to chat,

- a

Aaron Boodman

unread,
Jan 13, 2008, 2:06:57 PM1/13/08
to greasemo...@googlegroups.com
Thanks for the research, that is very good to know. This means that if
backwards compatibility suffers somewhat in FF2, there is a
workaround.

esquifit

unread,
Jan 13, 2008, 3:26:00 PM1/13/08
to greasemo...@googlegroups.com
I am a bit confused by all this. Anyway, I think the problem does not
only affect the GM_* API; if one attempt to enumerate the members of
the gmScope object in Anthony's example, one gets:

* window: [object XPCNativeWrapper [object Window]]
* unsafeWindow: [object Window]
* document: [object XPCNativeWrapper [object HTMLDocument]]
* XPathResult: nsIDOMXPathResult
* GM_addStyle: function (css) { GM_addStyle(safeDoc, css); }
* GM_log: function () { <the code>}
* GM_setValue: function () { <the code> }
* GM_getValue: function () {<the code> }
* GM_openInTab: function () { <the code>}
* GM_xmlhttpRequest: function () { <the code>; }
* GM_registerMenuCommand: function () { <the code> }
* addEventListener: function XPCNativeWrapper function wrapper() {
[native code] }
* top: [object XPCNativeWrapper [object Window]]
* self: [object XPCNativeWrapper [object Window]]
* setTimeout: function XPCNativeWrapper function wrapper() { [native code] }
* alert: function XPCNativeWrapper function wrapper() { [native code] }
* getInterface: function XPCNativeWrapper function wrapper() { [native code] }
* navigator: [object XPCNativeWrapper [object Navigator]]
* location: <the url where you are testing this>
* console: [object Object]

Any of these objects can be overridden by the attacking page (I
confirmed it). I do not know whether jumping into the browser scope
is possible, or whether one could do nasty things with the console
object when it points to firebug's console. But even if we were not
able to redefine these functions we could still make things like
instructing the browser to open 1000000 tabs, cluttering the prefs.js
file with trash (well, prefs.js gets written back when exiting
firefox, but we could then exhaust the available memory by creating
enough entries in the prefs store), etc. So fixing GM_XHR does not
seems to be the definite solution, though the highest priority at any
rate.

RodMcguire

unread,
Jan 13, 2008, 3:40:04 PM1/13/08
to greasemonkey-dev
On Jan 13, 3:26 pm, esquifit <esqui...@googlemail.com> wrote:
> I am a bit confused by all this. Anyway, I think the problem does not
> only affect the GM_* API; if one attempt to enumerate the members of
> the gmScope object in Anthony's example, one gets:

Is that where userscript functions and variables are stored too?

Aaron Boodman

unread,
Jan 13, 2008, 3:51:20 PM1/13/08
to greasemo...@googlegroups.com

Yeah. I agree that XHR is the highest priority, that's what I'm focusing on.

- a

esqu...@googlemail.com

unread,
Jan 13, 2008, 6:38:10 PM1/13/08
to greasemonkey-dev
Apparently not. I had also tried setting a var foo='bar' in the
userscript, but both gmScope.foo and gmScope.window.foo returned
'undefined'.

RodMcguire

unread,
Jan 13, 2008, 10:29:43 PM1/13/08
to greasemonkey-dev
On Jan 13, 6:38 pm, "esqui...@googlemail.com"
<esqui...@googlemail.com> wrote:
...
> > Is that where userscript functions and variables are stored too?
>
> Apparently not. I had also tried setting a var foo='bar' in the
> userscript, but both gmScope.foo and gmScope.window.foo returned
> 'undefined'.

In some past discussion on this issue I recall someone saying that a
userscript was wrapped inside an anonymous function so that all
functions and variables lived there. I never heard a reason of why.

Aaron Boodman

unread,
Jan 13, 2008, 10:48:38 PM1/13/08
to greasemo...@googlegroups.com
On Jan 13, 2008 7:29 PM, RodMcguire <mcg...@telerama.com> wrote:
> In some past discussion on this issue I recall someone saying that a
> userscript was wrapped inside an anonymous function so that all
> functions and variables lived there. I never heard a reason of why.

Way in the past Greasemonkey scripts were wrapped inside an anonymous
function to prevent them from accidentally interacting with their
environment. This wasn't really a security thing but a sanity thing.
It was meant to prevent your script from breaking when the global
context of the content page changed. It didn't work well, those were
early days.

With mozilla's addition of the Sandbox object, Greasemonkey no longer
needed to wrap things in an anonymous function because the sandbox
does the right thing -- provide a totally separate context. But by
that time lots of scripts relied on the wrapper function by using the
"return" statement in top level code. So, I left it in for
compatibility.

But you're right, that explains why var foo = "foo" doesn't show up on
the sandbox.

- a

RodMcguire

unread,
Jan 13, 2008, 11:12:26 PM1/13/08
to greasemonkey-dev
Maybe Greasemonkey script headers need a "@version" entry. That way
new scripts that specify a @version live in a normal environment where
they can access the Global Object, while legacy scripts still get
wrapped in an anonymous function where a top level "return" works.

Having the Global Object accessible makes things a lot easier for
debuggers.

On Jan 13, 10:48 pm, "Aaron Boodman" <bo...@youngpup.net> wrote:

Aaron Boodman

unread,
Jan 13, 2008, 11:13:42 PM1/13/08
to greasemo...@googlegroups.com
Ok, I think I have a possible solution.

I modified GM_xhr to not send cookies for cross-domain requests in FF2
and lower. See patch here:
http://greasemonkey.devjavu.com/changeset?old_path=%2Fbranches%2F0.7%2Fsrc&old=593&new_path=%2Fbranches%2F0.7%2Fsrc&new=601
.

I tested this on many user scripts and compat looks good, but there is
clearly room for some problems, if scripts integrated with sites using
GM_xhr and expected login to work. Users of those scripts could
potentially be directed to update to FF3b.

The general idea is that although you can get to GM functions, you
can't do much damage with them. For example, you could fill up the
preferences store or open tabs, but I'm not as worried about those
kind of attacks.

===

Other approaches that I tried which didn't work:

Checking the call stack. The idea was that I would walk the stack
looking for a function whose __parent__ was from content. If found,
the request would be denied. This did not work because anonymous
functions do not have a __parent__ value. Therefore it is impossible
to tell the difference between anonymous functions from chrome and
those from content. It might be possible to work around this, but I
don't know how without help from Mozilla.

Next I tried disallowing access to cross-domain xhr after an access to
unsafeWindow. Unfortunately, I could not find a way to track access to
unsafeWindow. __defineGetter__ does not work work on Greasemonkey's
sandbox. This is the same wall that Johan has hit before
(http://ecmanaut.blogspot.com/2007/08/extending-greasemonkey-by-way-of.html).
Again, I need help from Mozilla to get any farther with this approach.

===

I'd like to push a build with this change soon, but I think that first
I should check with Mozilla to make sure I haven't missed anything on
the other two options.

In the meantime, what do people think of this fix?

- a

Jesper Kristensen

unread,
Jan 14, 2008, 2:10:13 PM1/14/08
to greasemonkey-dev
On 14 Jan., 04:48, "Aaron Boodman" <bo...@youngpup.net> wrote:
> With mozilla's addition of the Sandbox object, Greasemonkey no longer
> needed to wrap things in an anonymous function because the sandbox
> does the right thing -- provide a totally separate context. But by
> that time lots of scripts relied on the wrapper function by using the
> "return" statement in top level code. So, I left it in for
> compatibility.
>
> But you're right, that explains why var foo = "foo" doesn't show up on
> the sandbox.

So, is local variables to this function not available to the website,
but global variables in the sandbox is? If so, maybe the GM_*
functions could be made function local.

Aaron Boodman

unread,
Jan 14, 2008, 11:33:03 PM1/14/08
to greasemo...@googlegroups.com
On Jan 14, 2008 11:10 AM, Jesper Kristensen <goo...@jesperkristensen.dk> wrote:
> So, is local variables to this function not available to the website,
> but global variables in the sandbox is? If so, maybe the GM_*
> functions could be made function local.

This doesn't seem to work. I tried copying GM_* to a local and it
throws an exception when user scripts try to call it.

I talked to Mozilla today and they gave me a good idea for a better
fix using C++. I'm going to take a whack at it tonight (basically,
walking the js call stack in C++, looking for functions not from the
sandbox). That fix should work for all APIs and be completely
transparent to user script developers.

They are also going to try and add a different layer of security for
this problem from their side for 2.0.0.12, but no guarantees. Also, GM
would have to be updated to take advantage of that fix, it wouldn't
just start working automatically.

- a

Johan Sundström

unread,
Jan 15, 2008, 9:07:19 AM1/15/08
to greasemo...@googlegroups.com
On Jan 15, 2008 5:33 AM, Aaron Boodman <bo...@youngpup.net> wrote:
> I talked to Mozilla today and they gave me a good idea for a better
> fix using C++. I'm going to take a whack at it tonight (basically,
> walking the js call stack in C++, looking for functions not from the
> sandbox). That fix should work for all APIs and be completely
> transparent to user script developers.
>
> They are also going to try and add a different layer of security for
> this problem from their side for 2.0.0.12, but no guarantees. Also, GM
> would have to be updated to take advantage of that fix, it wouldn't
> just start working automatically.

Does either approach sever the __parent__ band trail that ties
unsafeWindow accesses and other stuff from the user script sandbox
stored on unsafeWindow?

I use the latter feature quite a lot, such as for adding DOM 0 style
features to my browser:

window.__defineGetter__("pages", function() { return
(scrollMaxY+innerHeight)/innerHeight; });

and exposing more complex APIs and features to select web pages from
code implementing them in Greasemonkey space -- occasionally even
using the privileged GM functions -- and it's sad to find them all
vulnerable to this huge exetent.

If they don't, I guess I'll live through it by upgrading to Firefox 3
myself, but it would be very nice to be able to share the good stuff
with others who haven't or won't for quite some time still, without
undermining their browser security.

Aaron Boodman

unread,
Jan 15, 2008, 11:51:45 AM1/15/08
to greasemo...@googlegroups.com
On Jan 15, 2008 6:07 AM, Johan Sundström <oya...@gmail.com> wrote:
> Does either approach sever the __parent__ band trail that ties
> unsafeWindow accesses and other stuff from the user script sandbox
> stored on unsafeWindow?

I didn't follow this, can you clarify?

> I use the latter feature quite a lot, such as for adding DOM 0 style
> features to my browser:
>
> window.__defineGetter__("pages", function() { return
> (scrollMaxY+innerHeight)/innerHeight; });

This one would work. Even if the code were something like this:

unsafeWindow.__defineGetter__("foo", function() {
// something that calls GM_*
});

I think it would still work because there is no function in that stack
which is defined in content (whose global object is unsafeWindow,
which is what I plan to test).

> and exposing more complex APIs and features to select web pages from
> code implementing them in Greasemonkey space -- occasionally even
> using the privileged GM functions -- and it's sad to find them all
> vulnerable to this huge exetent.

Yeah, sorry. I'm not having a blast either.

> If they don't, I guess I'll live through it by upgrading to Firefox 3
> myself, but it would be very nice to be able to share the good stuff
> with others who haven't or won't for quite some time still, without
> undermining their browser security.

I don't think that I will be reducing what you can do here any more
than what FF3 will do.

- a

Aaron Boodman

unread,
Jan 15, 2008, 12:33:23 PM1/15/08
to greasemo...@googlegroups.com
On Jan 15, 2008 8:51 AM, Aaron Boodman <bo...@youngpup.net> wrote:
> This one would work. Even if the code were something like this:
>
> unsafeWindow.__defineGetter__("foo", function() {
> // something that calls GM_*
> });
>
> I think it would still work because there is no function in that stack
> which is defined in content (whose global object is unsafeWindow,
> which is what I plan to test).

So just to be clear, the code I plan to implement would look something
like (in JS pseudo-code):

function allowAccessToGM() {
var fn = arguments.callee;
while (fn) {
if (!fnWasDefinedInGreasemonkey(fn) && !fnWasDefinedInSandbox(fn)) {
return false;
}
fn = fn.caller;
return true;
}
}

So I just realized that one thing this would break is if content
exposed a js api with a callback, and you used it and then called a GM
api.

I don't know how to make that use-case work, but I think this is a
pretty good tradeoff.

- a

Johan Sundström

unread,
Jan 15, 2008, 2:08:23 PM1/15/08
to greasemo...@googlegroups.com
On Jan 15, 2008 5:51 PM, Aaron Boodman <bo...@youngpup.net> wrote:
> On Jan 15, 2008 6:07 AM, Johan Sundström <oya...@gmail.com> wrote:
> > Does either approach sever the __parent__ band trail that ties
> > unsafeWindow accesses and other stuff from the user script sandbox
> > stored on unsafeWindow?
>
> I didn't follow this, can you clarify?

Ugh, I need to work those communication skills. ;-)

Looking back at Sunday, for a few moments I was hoping that Firebug
had already solved the problem of a __parent__ reference you could
follow into privilege land, as asking for console.__parent__ in the
Firebug console throws an "uncaught exception: Permission denied to
get property Object.__parent__" exception -- but Firebug (1.05)
doesn't make use of the sandbox Greasemonkey does, just assigning its
FirebugConsole object to window.console, which we wouldn't want to do
in Greasemonkey.

I wondered if we could achieve that or a comparable effect, but I
guess in at least your primary scenario, the attack will be stopped on
malicious invocation of something stolen from the GM scope, not before
trying to get at the data in the first place.

Rephrased, what I wondered about was not whether the my code example
would work, but whether it would be safe against an exploit in page
content looking like:

window.__defineGetter__("__defineGetter__",function ouch() {
GM = ow.caller.__parent__;
});

which, given my example code, would expose the GM scope in a global
variable of the same name. It still will, I guess, but invocations of
its members will fail?

> > I use the latter feature quite a lot, such as for adding DOM 0 style
> > features to my browser:
> >
> > window.__defineGetter__("pages", function() { return
> > (scrollMaxY+innerHeight)/innerHeight; });
>
> This one would work. Even if the code were something like this:
>
> unsafeWindow.__defineGetter__("foo", function() {
> // something that calls GM_*
> });
>
> I think it would still work because there is no function in that stack
> which is defined in content (whose global object is unsafeWindow,
> which is what I plan to test).

That's a bit encouraging to hear, though.

> So just to be clear, the code I plan to implement would look something
> like (in JS pseudo-code):
>
> function allowAccessToGM() {
> var fn = arguments.callee;
> while (fn) {
> if (!fnWasDefinedInGreasemonkey(fn) && !fnWasDefinedInSandbox(fn)) {
> return false;
> }
> fn = fn.caller;
> return true;
> }
> }
>
> So I just realized that one thing this would break is if content
> exposed a js api with a callback, and you used it and then called a
> GM api.
>
> I don't know how to make that use-case work, but I think this is a
> pretty good tradeoff.

Agreed; a lot better than I had hoped for with this approach, actually.

> > and exposing more complex APIs and features to select web pages from
> > code implementing them in Greasemonkey space -- occasionally even
> > using the privileged GM functions -- and it's sad to find them all
> > vulnerable to this huge exetent.
>
> Yeah, sorry. I'm not having a blast either.

No call for apologies, though; it's not like this issue was caused by
malice. :-)

arantius

unread,
Jan 16, 2008, 11:25:17 AM1/16/08
to greasemonkey-dev
Aaron Boodman wrote:
> I could not think of any fix for this that is backwards compatible. As
> a quick fix, the only thing I could come up with is a confirmation
> dialog before any cross-domain XHR.

I personally would prefer a confirmation dialog before access would be
permitted to unsafeWindow. I haven't yet tried to actually make this
work, though, so I don't know for sure if it's possible; but it seems
it should be straightforward enough to define a getter for
sandbox.unsafeWindow rather than a plain property. Then, each script
would remember whether the user had allowed it access to unsafeWindow
or not.

Or, perhaps, as a (slightly) breaking change: a new @header which opts-
in to using unsafeWindow at all. This could impose an extra paragraph/
button/dialog/whatever for scripts at install time.

Aaron Boodman

unread,
Jan 16, 2008, 11:39:02 AM1/16/08
to greasemo...@googlegroups.com
On Jan 16, 2008 8:25 AM, arantius <aran...@gmail.com> wrote:
> I personally would prefer a confirmation dialog before access would be
> permitted to unsafeWindow. I haven't yet tried to actually make this
> work, though, so I don't know for sure if it's possible; but it seems
> it should be straightforward enough to define a getter for
> sandbox.unsafeWindow rather than a plain property. Then, each script
> would remember whether the user had allowed it access to unsafeWindow
> or not.

I could not make getters on the GM sandbox object work. Johan had the
same problem here:
http://ecmanaut.blogspot.com/2007/08/extending-greasemonkey-by-way-of.html.
If you can figure it out, that would be great.

> Or, perhaps, as a (slightly) breaking change: a new @header which opts-
> in to using unsafeWindow at all. This could impose an extra paragraph/
> button/dialog/whatever for scripts at install time.

There is no way to reliably detect use of unsafeWindow for the same
reason that getters don't work. You could scan the text for
"unsafeWindow" but that can be gotten around.

- a

Anthony Lieuallen

unread,
Jan 16, 2008, 11:58:45 AM1/16/08
to greasemo...@googlegroups.com
On 1/16/2008 11:39 AM, Aaron Boodman wrote:
>> Or, perhaps, as a (slightly) breaking change: a new @header which
>> opts- in to using unsafeWindow at all. This could impose an extra
>> paragraph/ button/dialog/whatever for scripts at install time.
>
> There is no way to reliably detect use of unsafeWindow for the same
> reason that getters don't work. You could scan the text for
> "unsafeWindow" but that can be gotten around.

Let me elaborate. New metadata header. This header means "this script
wants access to the unsafeWindow object". At install time, GM detects
this header, presents extra disclosure to the user. If script with this
header is installed, a flag is set.

At script run time, only scripts with this flag set get the unsafeWindow
object in their scope. This unfortunately doesn't solve
wrappedJSObject, though, so perhaps it's a moot point. Perhaps this
flag could control whether you get /any/ special content (GM_*(),
unsafeWindow, anything else) in the sandbox. If there is none, there is
none to expose.

Johan Sundström

unread,
Jan 16, 2008, 3:41:38 PM1/16/08
to greasemo...@googlegroups.com
On Jan 16, 2008 5:58 PM, Anthony Lieuallen <aran...@gmail.com> wrote:
> Let me elaborate. New metadata header. This header means "this script
> wants access to the unsafeWindow object". At install time, GM detects
> this header, presents extra disclosure to the user. If script with this
> header is installed, a flag is set.

Rephrasing assuming we pick the above path, GM henceforth is neutered
to safe operations only, except for scripts where the user has
consciously flipped a flag that blesses the script with all the power
of present-day Greasemonkey.

(Plus implementation detail of having the script author provoke the
dialog via a @header, so the user would know that she has to say yes
*twice* before that script will work for her, probably waiting
patiently for three seconds both times.)

"Safe operations only" could be -- as you suggest -- neutering the
script's access to unsafeWindow and wrappedJSObject, assuming we
could, or, it could be allowing scripts access to content-only
privileges, neutering it of the dangrous power of all the
higher-privilege GM APIs.

I'm personally -1 about this kind of thing.

Aaron Boodman

unread,
Jan 18, 2008, 3:20:00 PM1/18/08
to greasemo...@googlegroups.com
Update: I haven't had time to work on this the last few days (I had to
go back to work) but I'll get back to it over the weekend.

- a

Reply all
Reply to author
Forward
0 new messages