evalの第二引数とGreasemonkeyのunsafeWindowについて
「http://d.hatena.ne.jp/brazil/20070420/1177060289」でFirefox専用の第二引数に環境の指定をとるevalの使い方について説明されています。その元はこちら「http://www.tom.sfc.keio.ac.jp/~sakai/d/?date=20070414#p02」で、僕もこれを見たときに初めてしりました。そのころ僕はグリモンのスクリプトをいじっていたので、ちょっと気になっていくつか試してみて、ネタ元のページに以下のようなブクマコメントをしました。
evalの第2引数しらなかった。これを応用すると、GreasemonkeyでusafeWindow.xxx() とする場合、xxx内部からeval("GM_xmlhttpRequest", arguments.callee.caller)でGM_xmlhttpRequestが取得されてしてしまう。usafeWindow経由で関数呼んじゃダメ。
http://b.hatena.ne.jp/entry/http://www.tom.sfc.keio.ac.jp/~sakai/d/?date=20070414%23p02
さらにそのメタブクマで、
さらにuser.js内部でunsafeWindow.jsonpcallback=function(){...} などとする場合、外部から eval("GM_xmlhttpRequest", jsonpcallback) でも GM_xmlhttpRequest が漏洩してしまう。これでドメインの壁が越えられて夢ひろがりんぐ…じゃなくて、超危険。
http://b.hatena.ne.jp/entry/http://b.hatena.ne.jp/sawat/20070416%23bookmark-4478097
で実際のところ、以下のuser.jsがGreasemonkeyにインストールおよび活性化されている場合、
// ==UserScript== // @name test // @namespace test // @include * // ==/UserScript== unsafeWindow.myobject = {};
攻撃者は以下のコードでGM_xmlhttpRequestを取得し、任意のURLへリクエストを発行できてしまいます。
gm_xhr = eval("GM_xmlhttpRequest", window.myobject);
あるいは、以下のようなuser.jsだった場合でも、
// ==UserScript== // @name test // @namespace test // @include * // ==/UserScript== with(unsafeWindow) { alert('Hello, World.'); }
以下のよう関数を上書きすることで、同様にGM_xmlhttpRequestオブジェクトを取得できます。
window.alert = function(str) { gm_xhr = eval("GM_xmlhttpRequest", arguments.callee.caller); };
今まではGM_xmlhttpRequestのが漏洩に関しては「関数の引数にGM_xmlhttpRequestを渡していけない」*1などがいわれていましたが、実際にはこのように「unsafeWindowのプロパティにオブジェクトを登録する」、または「unsafeWindowの関数を呼び出す」だけで GM_xmlhttpRequest が漏洩してしまいます。
さらに性質の悪いことに、Firefoxではゲッタ/セッタの定義、や watch関数といったものもあるため、user.js側が関数を呼び出しているつもりが無くても攻撃者の関数が呼び出されてしまうことがあります。
このようにunsafeWindowは文字通り「unsafe」です。極力、使用は避けた方がよいでしょう。これは Greasemonekyのセキュリティホールなのか、それとも単なるuser.jsの書き方の問題なのか微妙ですが、user.jsを書く人(できれば使う人も)は気をつけた方がいいと思います。
なお、以下の操作は問題ないようです。*2
- window.alert()など、unsafeWindowではなくwindowから関数を呼び出す。(攻撃者はそれらの関数を上書きできない。)
- Stringオブジェクトなどの組み込み型の関数を呼び出す。(攻撃者が組み込み型のprototypeを書き換えてもuser.jsの側には影響しない)
- DOM関係の関数を呼び出す。(それらの関数はラッパーに置き換えられているため、攻撃者が上書きしても影響されない。ただし、unsafeWindow.document.createElementのようにunsafeWindowから辿るのはNG。)
ちなみに今問題としているのは、主に上記の例のような @include に不特定多数のURLがマッチするような場合です。Livedoor ReaderやGoogleの検索結果に機能を追加するようなuser.jsの場合は、そもそも元のページにXSS脆弱性がない限り攻撃者のスクリプトが紛れ込むことが無いので、unsafeWindowを使用していても特段の問題は無いでしょう。