m2O

2011/05/19 (木)

Opera9.6 から「安全でなくなった」 UserJavaScript があるけどイコール「危険」というわけでもない話

|  Opera9.6 から「安全でなくなった」 UserJavaScript があるけどイコール「危険」というわけでもない話 - m2O を含むブックマーク


何を主張したいのか良くわからないタイトルだけど、以下のような話をします。

  1. 発端となる Opera9.6 の変更
  2. どんな UserJavaScript が「安全でない」か
  3. どんなリスクがあるか
  4. でも「危険」というわけでもないケースが多い
  5. @include の仕様と、できるだけ安全にするためのTips
  6. 特権コードを使う UserJavaScript はすべからく extension に移行すべき(審議中)

--

発端となる Opera9.6 の変更

d:id:os0x:20081120:1227165925 で2008年11月に既に紹介されているように、Function に caller プロパティが追加されました。

Function.caller

9.60 beta 1 RCからFunctionにcallerプロパティが追加され、関数の呼び出し元を参照できるようになりました。

arguments.callee.caller で UserJavaScriptのソース取得 - os0x.blog

このプロパティから、Function の呼び出し元の Function を取得することが出来ます。

function hoge() {
  alert(hoge.caller);
}
hoge(); // null (グローバル領域で実行した場合)
(function aa() { hoge(); })(); // function aa() { hoge(); }

これは JavaScript の機能追加であって、一般的な JavaScript 処理系で問題が起こることはありません。

ですが Opera の UserJavaScript ではページ側の script と UserJavaScript 側で同じ Object(window や document、その他全てのビルトインオブジェクト) を共用しているため、ページ側の script では許されていない特権的な処理をUserJavaScript でやっていると、ページ側の script からそれらのオブジェクトを経由して、その特権的な処理を乗っ取ることが出来てしまいます(回避方法はありますがここでは触れません)。

// UserJavaScript でクロスドメイン通信を行う function 
function xGet(url, callback) {
    var s = document.createElement('script');
    ... snip ...
}
// ページ側から document.createElement を書き換えることで UserJavaScript の xGet を取得する。
var evilXGet = null;
var _org_createElement = document.createElement;
document.createElement = function(){
    if (!evilXGet && /xGet/.test(arguments.callee.caller)) {
        evilXGet = arguments.callee.caller;
        document.createElement = _org_createElement;
    }
    if (evilXGet) {
        // ためしに Google トップページを取得。
        evilXGet('http://www.google.co.jp/', function(txt) { alert(txt.substring(0, 200)) });
    }
    return _org_createElement.apply(document, arguments);
}

Opera の UserJavaScript と同様な機能を提供する Firefox のアドインの GreaseMonkey では、window や ビルトインオブジェクトは全て別のオブジェクトに置き換えられ(ラップされ)ていて、使い方を間違えなければ安全に GM_xmlhttpRequest などの特権コードを使うことが出来ます。


どんな UserJavaScript が「安全でない」か

まず、ほとんどの UserJavaScript は安全です。

UserJavaScript はもともと「Opera というマイナーブラウザでうまく動かない(表示できない)WEBサイトを閲覧者側で何とかする」という目的で導入されました。( ユーザー JavaScript にて使用可能なメソッドとイベントAPI 郡から推測。)

ですから、ページ側の script で出来ること以上のことは基本的にできないようになっています。

例えば以下のような UserJavaScript が考えられますが、どれもページ側の script でもできることなので安全です。

  • はてブ数を表示する(img 要素を追加しているだけ)
  • リファラが Google の検索結果だったらリファラから検索語を取得してページ内のその文字を強調表示する(リファラはページ側の script で取得可能。後は自ページの DOM を操作するだけ)。
  • ニコニコ動画のどのページでもマイリストを操作する(同じオリジンのサーバーと通信)。

では「安全でない」とはどういったケースでしょうか。このエントリを書いている時点でわかっているのは以下の2ケースです。

  • UserJavaScript 内にユーザーIDやパスフレーズなど、個人情報や特別な処理を行うための情報を直接記述している。(前述の d:id:os0x:20081120:1227165925 で指摘されているもの。)
  • UserJavaScript固有の機能を利用して、別オリジンのサーバーと通信できるようにしている。(http://mattz.xii.jp/comment/60#comment-60id:edvakf が指摘しているもの。)

どんなリスクがあるか

UserJavaScript 内にユーザーIDやパスフレーズを直接記述した場合、それらが盗まれる危険性があります。

上記のような UserJavaScript は滅多に無いと思いますが、例としてあげるなら私の Auto Commit del.icio.us(紹介ページ) があります。
もしパスフレーズが盗まれると、悪意のページを開いただけで delicious にブックマークが追加されてしまいます。

このケースを見ただけではリスクはとても小さく見えますが、そのユーザーID/パスフレーズによって何が出来るかが、そのままリスクの大きさになります。


別オリジンのサーバーと通信する処理が乗っ取られると、あなたがログインしているWEBサイトのページがその悪意のページに読み取られることになります。

もっともシンプルな例を挙げると Google のトップページです。GMail にログインしていると右上にメールアドレスが表示されますよね。これが読み取られますから悪意のページからあなたの GMail アドレスがわかってしまいます。

最悪のケースでは、あなたがログインしているWEBサイトでパスワードの再入力無しにできることが、以下のシナリオで行われる可能性もあります。

  1. 何らかの処理(例えば退会処理)を行うページを読み取る。
  2. 読み取ったページのソースから、処理を行うためのパラメーター(ワンタイムトークン等)を取得する。
  3. 処理を行うURLに上記のパラメーターを含めてリクエストすると処理が完了する。

ただし、紹介したシナリオで、退会処理のように重要な処理が行えることはまずありません。
これは以下の理由によるものです。

  • 重要な処理を行うページは通常 https で提供されます。悪意のページは当然別ドメインですから、別ドメインから https のページにリクエストがあると Opera ブラウザは警告を表示します。
  • 重要な処理の場合、いまどきのWEBサイトではパスワードの再入力を求めます。パスワードがわからないかぎり、悪意のページは処理を実行することは出来ません。


でも「危険」というわけでもないケースが多い

「安全でない」 UserJavaScript がほとんど無いということは既にお話しましたが、さらに UserJavaScript が安全でなかったとしてもすなわち「危険」でもないということをお話します。

良識ある UserJavaScript の作者の方は、その UserJavaScript が実行されるページを @include で設定します。

// ==UserScript==
// @name          Name of UserJavaScript
// @include       http://www.google.com/search?*
// @include       http://www.google.co.jp/search?*
// ==/UserScript==

この例では Google の検索結果のページでしかこの UserJavaScript が読み込まれないことを保障します。つまり上で散々危険性を煽った「悪意のページ」の所業ですが、この UserJavaScript においては Google にしか行うことが出来なくなります。

もちろん UserJavaScript の実行を許可しているページに XSS 脆弱性があったり、(https でないと)ページが改竄されたりすることが全くありえなくも無く、その場合は前提が崩れてしまいますが、その場合はもっとひどいことが起こっていますので、多くの人が使用する有名な UserJavaScript でない限りは攻撃対象として狙われることはないと思われます。

ですから、あなたが UserJavaScript を使用する場合、どのスクリプトに対しても1度、使用するかどうかを判断する必要があるでしょう。

  • その UserJavaScript が「安全」(あなたが個人情報やパスワードを入力する必要が無い、別のWEBサイトと通信を行わない)かどうか。
  • 「安全でない」場合、@include で適用されるWEBサイトを、前述のリスクを踏まえて信用するかどうか。

先に紹介した「Auto Commit del.icio.us」のケースでは、パスフレーズを盗めるページがまさに「del.icio.us」本体なので、「安全で無い」けれども「危険で無い」典型的な例ですね。


@include の仕様と、できるだけ安全にするためのTips

以前、こんなエントリを書きました。

こんなスクリプトを書いたんだけど、

// ==UserScript==

// @name script - prevent external script

// @description prevent external script by hostname of page.

// @include http://codezine.jp/

// ==/UserScript==

(function(){

window.opera.addEventListener('BeforeExternalScript',function(e){

e.preventDefault();

},false);

})();

これだと http://b.hatena.ne.jp/entry/http://codezine.jp/ なんかでも適用されて困った。

ユーザーJavaScript で @include する時は、他のURLを含むURLに注意 - m2O - チーム俺等

実はこの問題は既に改善されていて、

// @include http://codezine.jp/

この場合は文字通り「http://codezine.jp/」のトップページでしか動かなくなっています。

他のページでも適用するには以下のようにワイルドカードを使用します。

// @include http://codezine.jp/*

ただ、依然として問題は残っていて、ワイルドカードがホントに何でもアリなところです。

例えば、Google 検索と Google Map で使いたい場合、

// @include http://*.google.co.jp/*

のような指定をしてしまうと、http://www.evil.com/?.google.co.jp/ のような URL でも動いてしまいます。

以下のように、面倒でも後方にだけワイルドカードをつけるようにすると安全です。

// @include http://www.google.co.jp/*
// @include http://map.google.co.jp/*

特権コードを使う UserJavaScript はすべからく extension に移行すべき

(審議中)

トラックバック - http://orera.g.hatena.ne.jp/miya2000/20110519

2011/02/12 (土)

fancybox とかで表示位置が下にズレる時の対処法

|  fancybox とかで表示位置が下にズレる時の対処法 - m2O を含むブックマーク

fancybox っていう、画像を画面中央に表示する jQuery プラグインがあって、Opera でこれを使っているサイトを閲覧すると画像が画面中央でなく下の方に表示される問題がありました。

f:id:miya2000:20110212114957p:image

(背景がグレーになった後、何も表示されない…んじゃなくて下の方に表示されている。)

それで「fancybox クソだなあ」とか思いつつ原因を調べてたんですが、fancybox は全然悪くなくて jQuery のバージョンが古く(1.2系)、$(window).height() がウィンドウサイズを返すのではなくてページの高さを返すからでした。fancybox さんゴメンナサイ。

fancybox - Opera 9.x hotfix patch - msg#00196 - Programming Mailing Lists


と言うわけで(上のパッチは使わずに) jQuery の方を user.js で直しましょう。jQuery の方を直すことで、fancybox 以外の不具合も解消するかもしれません。

jQuery1.3 以降では既に直っていますから対象は 1.2系だけです。修正方法はチケットに書かれていたのでそれをそのまま使います。

https://gist.github.com/823432

年月が過ぎてブラウザがバージョンアップしていくと、昔動いていたものが動かなくなったりする*1ので、サイト運営を続けていく過程ではライブラリのバージョンアップもやっていかないといけないのだろうと思いました。

*1:ブラウザがバグを修正した結果、バッドノウハウ的なものが淘汰される、というケースも多くあると思います。

トラックバック - http://orera.g.hatena.ne.jp/miya2000/20110212

2010/05/16 (日)

冗談かと思ったら本当に Opera のスクロールバーを消したら気持ち良かった

|  冗談かと思ったら本当に Opera のスクロールバーを消したら気持ち良かった - m2O を含むブックマーク

昨日行われた no titleUstream で見ていたんですが、そのときの lazy_dog さんのライトニングトークで「僕はスクロールバーを消している」という衝撃の使い方を教えていただきました。

なんでおいらは、その圧迫感を与えてしまうスクロールバーを消してみたら、何かいつの間にか、いつもよりタブ減った気がしました。きちんと (ずるずると) 読むようになった様です。最初は、マウスウィール (トラックボールなんで、Wheel Ball を使ってだけど) でスクロールするからスクロールバー邪魔だし、そもそもスクロールバーを触ってスクロールすることなんて滅多にないので消してみたんですけど、思ったより別のメリット浮き出た感じになりました。

狐に背中を飛び越されまして

(設定は「opera:config#Show ScrollBars」から)

「表示領域を広げるためにそこまでやるかー」というのが第一印象でしたが、やってみると確かに圧迫感が減った、というかスクロールバーが表示されていることにストレスを感じるようになってしまいました。もう知らなかったころには戻れないのですね。

で、すでにあまたさんのところで紹介してもらってますが、スクロールバーはスクロールバーで欲しい時もありますから、必要なときだけ表示するような User Javascript を書きました。

ondemand scrollbar
http://github.com/miya2000/user.js/raw/master/ondemandscrollbar.js

カーソルをページの右端に置くと、スクロールバー(っぽいもの)が表示されます(スクロールが必要ない場合は表示されません)。

f:id:miya2000:20100516142510p:image

スクロールバーをダブルクリックすると消すようにしました。もう一度画面右端をダブルクリックすると復活します。(Opera 10.50 以降ならドメインごとに表示・非表示を記憶します。)

ぜひ皆さんもスクロールバーを消してみてください!

--

Bookmarklet も書きました。
スクロールバーを消す Bookmarklet - m2

2010/05/05 (水)

Softbank モバイルのページを Opera ユーザーへ

|  Softbank モバイルのページを Opera ユーザーへ - m2O を含むブックマーク

ソフトバンクモバイル のページで、タブをクリックしてもスクロールが起こった後にまた同じタブに戻るというやつ(例えばHTC Desireで「対応サービス」をクリックしてみてください)、アレをどうにかします。

http://github.com/miya2000/user.js/raw/master/softbank_for_opera.user.js

Opera ユーザーにはソフトバンクの人あんまりいないんですかね。

--

(追記)
Browser Javascript が有効になっていると再現しない…というか別のエラーが出ているようです。

browser.js L1730~

	} else if(hostname.indexOf('mb.softbank.jp')!=-1){			// PATCH-22, Softbank shop uses reserved variable name parent
		(function(){var the_parent;
			opera.defineMagicVariable('parent', function(){return the_parent;}, function(o){the_parent=o;});
		})();
		
			if(self==top)postError.call(opera, 'Opera has modified the JavaScript on '+hostname+' (Softbank shop uses reserved variable name parent). See browser.js for details');
	}

意味が良くわからないです。(せめて var the_parent = window.parent; なら...)

Browser Javascript については ページが見つかりません | Opera を参照してください。
これを無効にするには、アドレスバーから「opera:config#Browser JavaScript」へ移動して、値を 0 にします。

Opera6.ini にある Browser JavaScript の値を 0 に設定する事で、ブラウザ JavaScript を無効化し、修正をテストすることができます。簡単にこの動作を行うには opera:config#Browser JavaScript にアクセスしてください。この opera:config の設定の変更には Opera の再起動は必要ありません。

ページが見つかりません | Opera

現在は「Opera6.ini」から「operaprefs.ini」に名前が変更されています。

トラックバック - http://orera.g.hatena.ne.jp/miya2000/20100505

2010/04/02 (金)

「CSS によるブラウザ履歴の漏えいを防ぐ取り組み」についての Opera への要望

|  「CSS によるブラウザ履歴の漏えいを防ぐ取り組み」についての Opera への要望 - m2O を含むブックマーク

CSS によるブラウザ履歴の漏えいを防ぐ取り組み | Mozilla Developer Street (modest)

プライバシーの面ではとてもよいことだと思うし、Opera もこれを取り入れていくんではないかと思います。

ただ ndr でこの「CSS によるブラウザ履歴」を使って動画を視聴済みかどうかの判定しているので、Opera には是非ユーザーがサイトごとに選択できる実装を望みます。

いやほんとお願いしますよ。

os0xos0x2010/04/03 03:59せっかく10.50でlocalStorageやWebSQLDatabaseが実装されたので、自前で履歴管理するのもありでは?

miya2000miya20002010/04/03 10:27収集した情報に対する責任がこっちにまわってきちゃいますから、できればやりたくないですね。まあでもユーザーがそれを許容してくれるならOKかな。

miya2000miya20002010/04/03 11:28そうそう、あと ndr は Unite でやるのが筋なのだろうと考えていて、そうするとせっかく user.js から Unite にしたのに視聴履歴を得るためにまた user.js が必要になるというおかしな感じになりそうです。うーん。

トラックバック - http://orera.g.hatena.ne.jp/miya2000/20100402