Home > Latest topics

Latest topics 近況報告

たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。

萌えるふぉくす子さんだば子本制作プロジェクトの動向はもえじら組ブログで。

宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

Page 1/248: 1 2 3 4 5 6 7 8 9 »

拡張機能におけるeval()の5つの間違った使い方 - Nov 28, 2009

以下、Adblock Plus and (a little) more: Five wrong reasons to use eval() in an extensionのいいかげんな訳です。XUL/Migemoのバージョンアップ時のエディタによるレビューのコメントで「今回は公開を承認するけど、次からはeval()はなるべく減らすように。詳しくはこれを読んで。」と指摘されたので、自分が読むために訳してみました。誤訳があったら指摘して。一部のサンプルコードは見やすさのためにインデントを勝手に加えてます。

ちなみに、僕は5番目の点(こういう用途でeval()を使うなという話)については反対の立場です。拡張機能同士を協調して動作させたいなら、むしろeval()を使って関数を書き換えるやり方を使う方が望ましいとすら考えています。なので参考のために、似たような立場と思われるSimon氏・Dorando氏のコメントも訳しています。

2010年2月8日追記。このエントリで述べられている内容に対する反論を公開しました。できればそちらも併せてご覧下さい。


過剰に使われているJavaScriptの機能のひとつに、eval()関数があります。私はそれが非常に多くの拡張機能で利用されているのを見てきましたが、そのうちのごく一部だけが正しい使われ方をしています。ですので、eval()のすべての間違った使い方について説明したいと思います。

1. JSON形式のデータのパースのため

今日において、JSONはデータを保存するためのポピュラーな形式となっています。その最大の特長は、パースが非常に簡単であるということです。単に data = eval(json) という風に書けば、それだけで事足ります。

うまい話には裏があるんじゃないの? その通り。このjsonという変数は {foo: "bar" + alert(Components.classes)} のような内容が含まれるかもしれず、このようなJavaScriptのコードを実行してしまうと、あなたが意図していなかった結果になってしまうでしょう。このように、信頼できない情報源からやってきたデータをJSONとしてパースする用途にはeval()は全く不向きです。それがFirefoxの拡張機能であるなら、どんなWebサーバから送られてくるデータも信頼できません。もしそれがあなたのWebサーバであっても、ハックされて(※訳註:原文ではhacked)いるかもしれませんし、ユーザへの通信経路上で情報が改竄されているかもしれません(特に、暗号化されていない接続では)。あなたは、ユーザを危険な状況に晒したくはないでしょう。

それだけではありません。そのデータが拡張機能自身によって(例えば、ブラウザ終了時の状態を保存するためなどの目的で)書き出されたデータであっても、常に信頼できるとは限りません。その中にはひょっとしたら、Webから受け取ったデータが含まれるかもしれません。もしJSONを書き出す処理にバグがあって、JavaScriptの文字列として書き出すべき物が文字列になっていなければ、それをJSONとして解釈しようとした時、知らない間にJavaScriptのコードとして実行されてしまうでしょう。これが、JSON処理専用の機能を必ず使うようにした方が良い理由です。JSON処理専用の機能は、不正なデータを受け取った時にもJavaScriptのコードとして実行してしまわないので安全です。

2. プロパティ名が動的に変化する時に、そのオブジェクトのプロパティにアクセスするため

obj.fooNNnという変数の値である、というプロパティにアクセスしたいとき、どんなコードを書けばよいでしょうか? これは、あなたがアクセスしなければいけないプロパティの名前が事前には分からなくて、動的に決定されるものであるという場合のことです。拡張機能の中には、これを eval("obj.foo" + n) のようなやり方で解決しているものがあります。この時、その拡張機能はnの値の中に危険な内容が含まれていないかを検証する必要があるでしょう――でも、どうやって?

幸いにも、この質問の答えを考える必要はありません。もっといい方法があります。JavaScriptではすべてのオブジェクトが連想配列である(※訳註:原文ではassociative arrays)ことを思い出してください。言い換えると、obj.fooobj["foo"] は全く同じ意味で、すべてのプロパティは配列の要素としてアクセスできるのです。ですから、前述のような問題を解決するには単に obj["foo" + n] とだけ書けばよく、この操作は、何も他の余計なことをすることなく常にそのプロパティにアクセスするでしょう。

では、メソッド(関数)の場合は? JavaScriptではメソッドも、値が関数オブジェクトであるという違いがあるだけのただのプロパティです。その関数を this が正しい値を示すようにして呼び出すために、Function.call() というメソッドが利用できます:

var method = obj["foo" + n];
method.call(obj, param1, param2);

あるいは簡潔にこう書くこともできます:

obj["foo" + n](param1, param2);

同じアプローチが、グローバル変数やグローバルな関数に対しても使えます。「グローバルオブジェクト」のすべてのプロパティはwindowのプロパティとして参照できます。window.foowindow["foo"] は、グローバル変数fooの値を返すでしょう。(※訳註:JavaScriptコードモジュールなどwindowが使えない変数スコープでも、 (function() { return this; })() とすればその実行時の変数スコープのグローバルオブジェクトを取得できます。)

3. 関数に対して、その関数が処理を終えたあとに何をするべきかを示すため

私が時々見かけるひとつのパターンは、このような関数の呼び出し方です:

foo("window.close()");

その関数は他の場面で、異なるJavaScriptのコードをパラメータとして渡されていました。そして関数が処理を終えた後で、パラメータとして渡された内容を動作の指定として eval() で実行するようになっていました。

どう見ても、ここにはセキュリティ上の問題はありません(※訳註:もちろん、パラメータで渡す内容にWebから取ってきたデータが含まれる可能性がある時は問題外ですよ!)。では、このアプローチの一体どこが間違っているのでしょうか? 実際には、以下のような問題があります:

  • このコードはeval()が呼ばれるまでコンパイルされないでしょう。これは、それ以外の部分のコードについてはスクリプトが読み込まれた時にすぐにJavaScriptインタープリタが文法エラーを報告するのに対して、関数のパラメータとして渡されたコードの文法エラーは後になってからしか報告されないため、そのコードが実行されるような経路を辿る操作をあなたがテストしなかった場合に、問題が見過ごされてしまうだろうということを意味します。
  • もう1つの問題は、そのコードの中で起こったエラーに対して、JavaScriptインタープリタが正しいソースファイルと行番号を報告することができず、どこで問題が起こったのかを知ることができないという点です。このようなエラーのデバッグはとても面倒です。
  • 付け加えると、foo()に対して実行して欲しいコードをパラメータとして渡すというのは普通のやり方ではなく、見苦しい回避方法を色々と必要とします。(※訳註:ダブルクォーテーションをエスケープしないといけない、など。)

幸いにも、クロージャを使うことによってそれらの問題は解決できます(※訳註:この例はクロージャではなく関数リテラルとか関数オブジェクトとかその辺の話だと思うんですが……)。以下は、前述のコードを少し書き換えた例です:

foo(function(error)
{
  alert(error);
  window.close();
});

そしてfoo()という関数の内容は以下のようになるでしょう:

function foo(callback)
{
  ...
  callback("Full success");
}

4. HTMLやXULにインラインで記述されたイベントハンドラを実行するため

以下のようなボタンがあると仮定しましょう:

<button id="button" oncommand="doSomething();"/>

このイベントハンドラを実行するために eval(document.getElementById("button").getAttribute("oncommand")) としてはいけないのは何故でしょうか? その要素が実際にクリックされたなどの場合以外の所でイベントハンドラを実行するための方法として、拡張機能の中ではしばしばこのようなやり方が用いられます。しかしながら実は、commandイベントを生成する方法のほうがもっと簡単で、しかもイベントハンドラがどのように定義されていようとも正常に動作することが期待できます:

document.getElementById("button").doCommand();

doCommand()というメソッドは、すべてのXUL要素で利用可能です。他のイベントに対しては、document.createEvent()を使って本当のイベントオブジェクトを生成する方がよいでしょう――何故なら、イベントハンドラがそれを期待しているでしょうから。例えば:

var event = document.createEvent("MouseEvents");
event.initMouseEvent("click", true, true, window,
                     0, 0, 0, 0, 0,
                     false, false, false, false,
                     0, null);
document.getElementById("button").dispatchEvent(event);

では、あなたが「onfooaction」という風な独自の属性を定義していて、それがいかなる実際のイベントとも関連付けられていない場合には? このような場面でも、eval()を使うのは最良の選択とは言えません。何故なら、eval()を呼び出した関数の実行コンテキストにおいてコードが実行されてしまうからです。もしそのイベントハンドラがfooというグローバル変数を参照したとして、あなたがそのイベントハンドラを呼んでいる関数の中にfooという名前のローカルな変数があったら――そのイベントハンドラは意図しないままにそのローカル変数にアクセスしてしまうでしょう。そしてもちろん、その時イベントハンドラに対してパラメータを渡すこともできません。よりベターな解決策は、そのイベントハンドラのための関数を作る事でしょう:

var handler = new Function("param1", "param2",
                           document.getElementById("button")
                                   .getAttribute("onfooaction"));
handler("foo", "bar");

このシナリオでは、このイベントハンドラは「foo」をparam1という名前の引数として、「bar」をparam2として受け取るでしょう。(これは、よくあるインラインで記述されたイベントハンドラに対してeventというパラメータを渡す時にも使えます。)

5. ブラウザの関数を書き換えるため

以下のようなことをしているコードをよく見かけます:

gBrowser.foo = eval(gBrowser.foo
                            .toString()
                            .replace("foo", "bar"));

このようなやり方でブラウザの関数を書き換えている人は、公の場所でおしりペンペンされることをお勧めします。それは、ブラウザの関数を新しい関数で単に置き換えてしまうだけの拡張機能に比べてほんのちょっとだけマシであるに過ぎません。どちらの場合も、書き換えられようとしているコードが変化しないことを前提にしていますが――しかし、もしそれが起こったら? 最良のケースでは、その拡張機能は大きな損害を与えることなく単に動作しなくなるでしょう。しかしひょっとすると、ブラウザを壊してしまうかもしれません。あるいは、ブラウザのその関数がセキュリティ上の問題の修正のために書き換えられたとしたら、その拡張機能は同じセキュリティ上の問題をまた持ち込んでしまうかもしれません。

言い換えると――この使い方はしてはいけません。ほとんどの場合で、このアイデアはブラウザの関数の動作を変えるのではなく、その関数の前後に追加の処理を挿入するために使われています。幸いなことに、それをするためのもっと危険でないやり方として、ここでもクロージャを使えば元の関数を単純にあなたの関数で包み込むことができます:

var origFoo = gBrowser.foo;
gBrowser.foo = function(param1, param2)
{
  if (param1 == "top secret")
    doSomethingBeforeFoo();
  var result = origFoo.apply(this, arguments);
  if (result == null)
    doSomethingAfterFoo();
  return result;
}

元の関数にすべてのパラメータを渡すためにFunction.apply()を使うことに注意してください。その関数が今現在2つだけパラメータを受け取っているとしても、将来のバージョンのブラウザでは変わるかもしれません。あなたの拡張機能は新しいパラメータに対して何をすればよいのか知っているかもしれませんが、元の関数の動作を壊さないために、それらの新しいパラメータはそのまま元の関数へ引き渡しましょう。

eval()の正しい使い方とは?

私は、eval()関数の有効な使い方はそれほど無いと思っています。拡張機能の中にはユーザに対して、評価可能なJavaScriptのコードを入力させることを許しているものもあります。そのスクリプトが関数のパラメータとして値を受け取る必要があり、関数を作り変数を渡すのにFunction()コンストラクタを使うことが依然として望ましいとしても、これはeval()の妥当な使い方でしょう。

もう1つのeval()の使い方として、状況に応じて定数を宣言するためにも使えます:

if (typeof MY_CONSTANT == "undefined")
  eval("const MY_CONSTANT = 'foo'");

こうすることによって、もし他のスクリプトで同じ名前の定数が定義されていても、あなたは文法エラーを目にしなくて済むでしょう。しかしながら、私はこれはその場しのぎのやり方だと思います。もし同じ名前空間で実行される未知のスクリプトと衝突することを恐れているのなら、あなたは他のスクリプトが使わないような一意な名前を定数(グローバル変数も)に与えるように気をつけるべきです。また、あなた自身のスクリプトについても、定数宣言を含んでいるスクリプトを複数回読み込まないように気をつけてください。

最後に、実行時にそれ自身のコードを生成するためにeval()を大量に使う、分かりにくい・「圧縮」されたスクリプトもあります。Webにおいては「圧縮された」スクリプトに価値があることは認めますが、拡張機能の中で同じ事をやることにはほとんど意味がありません。拡張機能は1度だけしかダウンロードされませんから、ダウンロードにかかる時間をたった2秒だけ節約できても、誰も喜ばないでしょう。また、「圧縮」されたスクリプトはロードされ実行される度に毎回、処理に余計な時間を食うことでしょう。

エントリにつけられたコメント

1. Simonによるコメント

5番目について。関数の中のコードに手を入れる(単にコードの前後に処理を付け加えるだけでは実現不可能で、関数全体を書き換えないといけないような場合)方法として、どんなやり方なら勧められるというのでしょうか? また、ある1つの拡張機能があなたの推奨しているやり方を実行したら、関数の中身を書き換えるタイプの他のすべての拡張機能の動作が妨げられ、それらの拡張機能はすべてのコードを書き直すことを強いられるので、拡張機能同士が円満に共存できなくなるでしょう。

私は元々はあなたが勧めているようなやり方を取っていましたが、しかしeval()を使うことによってしか解決できないような非互換性の問題に何度か遭遇してきました。より良いやり方があるのなら私は喜んでそれを採用するつもりですが、残念ながら、私にはあなたが勧めているやり方が問題を解決してくれるようには見えません……

4番目について。new Function("some code")eval("some code")と比べてどのように安全なのでしょうか? bug 477380であなたはeval()を禁止することを提案していますが、new Function()も禁止しないと無意味なのではないでしょうか。

Wladimir Palantによる返信:

あなたが書いたわけではない関数の中身に対して変更を行う事は、一般的に言ってよいアイデアではありません。あなたがどんな風にそれをやったとしても、必ず酷い結果になるでしょう。他の手段(例えばObject.watch()など)であなたがやりたいことを実現できないのであれば、多分あなたはそれをするべきではないのでしょう。

new Function()について。それはeval()に比べて本質的に安全であるとは言えませんが、それはたいていの場合静的なコードのために使われ、それほど多くの問題を持ち込みません。これは、トラブルを抱え込まないようにするための良いコーディングの習慣ということです。

2. Mookによるコメント

特権が無いWebページの場合でも同じ事が言えるのでしょうか? Gecko 1.9.1はグローバルなJSONオブジェクトを実装するそうですが、evalInSandboxと同等のものはあるのでしょうか?

evalInSandboxを使うためにUniversalXPConnect権限が必要なのは困ります……

Wladimir Palantによる返信:

クロスサイトスクリプティングを防ぐことについて話しているのだと思いますが――スクリプトのためのサンドボックスは十分な対策とは言えません。これについてはbug 341604(※訳註:IEの独自拡張であるiframe要素のsecurity属性の実装についてのバグ)とWHATWGのWeb Appsの仕様のiframe要素のsandbox属性を参照してください。

5. Dorandoによるコメント

5番目について、例えば元の関数 gBrowser.foo について以下の場合を仮定しましょう(似たようなコードはMozillaのコードベースから簡単に見つけられます。例えばtabbrowser.xmlで「.isTrusted」や「permitUnload」を検索してみてください):

gBrowser.foo = function(param1, param2)
{
  if(!gBrowser.isItSaveToDoSomethingBeforeFoo())
    return;

  if (param1 == "do_nothing")
    return;

  var color = "red";
  doSomething(color, param2.length);

  if (param2 == "some_extensions_want_to_prevent_this")
    doSomethingAnnoying("dark"+color);
}
  1. 関数内でdoSomething()が実行された時に、常にdoSomethingAfterFoo()を実行するようにしたい、という時はどうすればいいのでしょうか?
  2. 関数内でdoSomething()が実行されるであろう時に、常にdoSomethingBeforeFoo()を実行するようにしたい、という時はどうすればいいのでしょうか? 元のコードのうちいくらかを複製することはできますが、それは既に修正されたセキュリティの問題をまた持ち込みかねず、関数を単に書き換えるだけにしない事のメリットを打ち消してしまいかねません。関数に対して行われる変更を常に参照し続けることは、いくつかのシチュエーションでは上手く行くでしょうが、私に言わせてもらうとそれは大変すぎます。(そしておそらくコードをいくらか遅くさせるでしょう。)
  3. gBrowser.foodoSomethingAnnoying()を実行するのを食い止めたい時はどうすればいいのでしょうか?
  4. gBrowser.fooが使う色をgreenに変えたい時はどうすればいいのでしょうか? 追加の関数を置き換えてその呼び出し元関数を確認するようにするという手もありますが、私にとってはそれは何かを壊してしまう可能性を増大させるものでしかないです。関数に本来の処理を行わせた後でその変更を後から取り消すというやり方も、可能ではありますが、良いユーザ体験をもたらすものとは思えません。

どちらの場合も、書き換えられようとしているコードが変化しないことを前提にしていますが――しかし、もしそれが起こったら?

maxVersionはそのために存在しています。

あるいは、ブラウザのその関数がセキュリティ上の問題の修正のために書き換えられたとしたら、その拡張機能は同じセキュリティ上の問題をまた持ち込んでしまうかもしれません。

isItSaveToDoSomethingBeforeFooが導入されて、doSomethingBeforeFoo();eval()でパッチを当てられた場合と同様の内容を含むようになったとしたら、(※訳註:ここで推奨されているやり方の方の)関数の置き換えでも同じ問題が起こり得ます。

その一方で、拡張機能の作者はeval()によって、(セキュリティに関するものも含めて)バグがFirefox本体側で修正されるよりも前にパッチを適用することができます。そのバグが本体側で修正された後は、書き換え対象のコードが見つからなくなるので、eval()によるパッチ適用は望ましい形で失敗する(※訳註:何の変化ももたらさず悪影響も及ぼさないということ)でしょう。

あなたが書いたわけではない関数の中身に対して変更を行う事は、一般的に言ってよいアイデアではありません。

もちろんです、それは組み込みのどんな機能の挙動を変えることについても常に言えることです。しかし拡張機能の作者にとっては、やりたいことを実現するための組み込みのインターフェースや関数が存在しない時に、それを実現するための唯一の手段がこれであるという場合もあります。

Simonが既に指摘していることの繰り返しになりますが、関数へパッチを当てるのではなく関数を丸ごと置き換えるという(※訳註:ここで推奨されている)やり方は、私が述べたようなことをやろうとしているすべての拡張機能に対してそれを完全に妨げてしまいます。ですからどうか、あなたが代替になる方法を提案できないのなら、このような手法を推奨しないでください。

あなたがどんな風にそれをやったとしても、必ず酷い結果になるでしょう。

(以下翻訳中)

今まで私がみてきた限りでは、ほとんどの最小の変更がそれを壊しうるという事実にもかかわらず、(APIの変更を含む)他の種類の変更によってコードが動かなくなるケースの方が、eval()によるパッチの適用が原因で動かなくなるケースよりも多く見られました。

Wladimir Palantによる返信:
  1. あなたはgBrowser.foowindow.doSomethingの両方を拡張する必要があります。gBrowser.fooの開始時にフラグ変数をfalseにセットして、window.doSomethingがそれをtrueにする、doSomethingAfterFooはそのフラグ変数がtrueになった時にだけ呼ばれる、という具合です。
  2. 私はあなたが、予知能力を持ったモジュールを必要としているとは思いません――あなたがやりたいのは、gBrowser.foowindow.doSomethingを呼んだ時にdoSomethingBeforeFooを呼ぶ、という事でしょう(window.doSomethingが他の場所から呼ばれた場合を除いて)。このような場合、やはり両方の関数を拡張するべきです。gBrowser.fooの開始時にフラグ変数をtrueにセットして、終了時にそれをfalseにセットする。window.doSomethingの開始時に、そのフラグ変数の値がtrueであるならdoSomethingBeforeFooを実行する。という具合です。
  3. bのやり方と同じ手法でgBrowser.fooを拡張し、doSomethingAnnoyingについてもそのフラグ変数がtrueな時は何もしないように拡張することで、可能でしょう。ただし、あなたが本当にそれをする必要があって、それが何をどのように壊すのかについては、改めてよく考えてください。
  4. bのやり方と同じ手法でgBrowser.fooを拡張し、doSomethingについてもそのフラグ変数がtrueで且つarguments[0]"red"である場合にarguments[0]"green"に置き換えるように拡張すれば、それも可能でしょう。ただしその場合も、やる前にその影響をよく考えてください。

maxVersionの仕組みについては、大抵の拡張機能は「バージョン3.0.*に対して互換性がある」という風に指定されているので、セキュリティの修正やマイナーリリースでの変更をキャッチアップしてくれません。

その一方で、拡張機能の作者はeval()によって、(セキュリティに関するものも含めて)バグがFirefox本体側で修正されるよりも前にパッチを適用することができます。――このような方法でバグにパッチを当てるのは、実に宜しくないアイデアです。Bugzillaにバグを報告して、ちゃんとしたパッチを書いてください。それが確かに明らかにバグで、安全に修正できるのであれば、その修正はマイナーリリースに取り込まれるでしょう。もしあなたが間違ったことをしていれば、フィードバックを得られるでしょう――それは、誰にも何も尋ねずに「パッチ適用」をしてブラウザの機能を壊してしまうよりも良いことです。

6. Dorandoによるコメント

doSomething(あるいは他の関数)は、 Components.classes[""].createInstance().doSomething(); という風な(※訳註:置き換え不能なネイティブのメソッドを呼ぶ)コードかもしれませんし、関数ではなくべた書きされたコードのブロックかもしれませんし、あるいは、その処理がとても頻繁に呼ばれる機能である場合は置き換え後の関数によって度し難いほどの余計な処理時間がかかるかもしれません(特に、複数の拡張機能がそういうことをするのなら)。

maxVersionの仕組みについては、大抵の拡張機能は「バージョン3.0.*に対して互換性がある」という風に指定されているので、セキュリティの修正やマイナーリリースでの変更をキャッチアップしてくれません。

これは「必ず酷い結果になる」という未来予測への反論ですよ。あなたが言っているのは実際には拡張機能の作者の過ちです。また、私に言わせれば、既存のコードを動かなくしてしまうような変更がMozillaのセキュリティアップデートで行われることがあまりに多すぎます(私が知っている最近の例はBug 442333(※訳註:eval()の第2引数の廃止を決定したバグ)です)。

このような方法でバグにパッチを当てるのは、実に宜しくないアイデアです。Bugzillaにバグを報告して、ちゃんとしたパッチを書いてください。

既にBugzillaに報告されていたり、次のメジャーリリースに修正が取り込まれることが確定していたりするなら、同じコードを使えるでしょう。そのバグが外部の(※訳註:拡張機能などの)コードに対してのみ影響を与えるものである場合は特に、(パッチが提出されていても)そのバグが実際に修正されるまでには何ヶ月もかかることがあります。

それが確かに明らかにバグで、安全に修正できるのであれば、その修正はマイナーリリースに取り込まれるでしょう。

その修正が次のマイナーリリースに取り込まれたと仮定しても、それには最低でも1ヶ月はかかります。その修正内容をバックポートすることはそれほど害を及ぼさないでしょう。

~誰にも何も尋ねずに「パッチ適用」をしてブラウザの機能を壊してしまうよりも良いことです。

もしその拡張機能の意図するところが、そのシチュエーションにおいて行われる通常の挙動を変更する事なのであれば、それは初期状態の挙動を壊してしまおうとするもののように見えるでしょう。

7. Simonのコメント

ちなみに、異なる実行コンテキストでコードを動的に実行することは、eval()のもう1つの妥当な使い方です。

eval("code to inject", context);

あるいは

domWindow.eval("code to inject");

これらは、コードを一時ファイルに書き出して、サブスクリプトローダーで読み込ませ、一時ファイルを削除する、という風なやり方によってのみ置き換えることができます。なぜなら、サブスクリプトローダーは依然としてdata: URIの読み込みを許可していないからです。(この回避策は実に面倒です。テンポラリファイルを使うことは他の問題を引き起こしかねませんから。)

Wladimir Palantによる返信:

「異なる実行コンテキスト」というのは、「特権がないコンテキスト」という意味ですか? それは別物です、そのコードはChrome権限を得ることはないでしょう。

8. Simonのコメント

「異なる実行コンテキスト」というのは、「特権がないコンテキスト」という意味ですか?

そうとは限りません。nsIWindowMediatorによって返されたDOMWindowのコンテキストは、Chrome権限のあるオブジェクトと同じになり得ます。(サブスクリプトローダーを使って読み込ませたスクリプトや、XBLのバインディングの中に書かれたスクリプトのようにwindowオブジェクトを汚染することなく動的に使われるスクリプトにおいては。)

最初の影響を見るには、以下のコードをエラーコンソールで実行してみてください:

Components.classes["@mozilla.org/appshell/window-mediator;1"]
          .getService(Components.interfaces.nsIWindowMediator)
          .getMostRecentWindow("navigator:browser")
          .eval("location");

自分で自分を危険に晒す行為 - May 19, 2008

塞がれたセキュリティホールを開けろという人々

extensions.checkCompatibility(対応バージョンの照合を行うかどうか)とextensions.checkUpdateSecurity(自動アップデートが安全に行われることを求めるかどうか)のうち、前者はまあfalseにしてもいいと思うけど、後者をfalseにするのは僕もいただけない。前者は「気をつけていれば被害を防げる」性質の物だけど、後者は「気をつけていても被害に遭う」性質の物だから。

自分でもよく分かってないものを人に勧めるな、とは言わない。それを言ったら自分も何もできなくなるから。その代わり、良くないことを勧めてしまったことに気がついたのなら、それを広めた時と少なくとも同じだけの労力を割いて訂正情報を広める努力をする(誤情報を伝えたエントリやコメントに正しい情報へのポインタを追記することもその一つ)のが、自分のしたことに対する責任の取り方という物だと僕は思ってる。

XML署名とハッシュを使って安全な方法でアドオンを更新できるようにする - Sep 21, 2007

Firefox 3からは、安全な方法での自動更新に対応していないアドオンはインストールできなくなるようだ、ということを先日書いた。そっちのエントリに詳しいことを書いたんだけど、要約すると、「安全な方法での自動更新を提供する」方法には以下の3つのパターンがある。

  1. Mozilla Add-onsにアドオンを登録し、そちらの自動更新の仕組みを利用する。
  2. HTTPSで通信できるサーバを自分で用意して、そこで自動更新の情報を含んだRDFデータソースとXPIパッケージを公開する。
  3. 自動更新の情報を含んだRDFデータソースに、XPIパッケージのハッシュ値を記述した上で、XML署名を施す

くでんさんの報じる所によると、このうち3番目の方法をものすごく簡単に実行できるようになる開発者向けのツール「McCoy(マッコイ)」が公開されたそうで、早速自分も使ってみたところ、本当に簡単だった(手順としては。作業はなんだかんだで手間取ったけど……)。なので、せっかく須藤さんにCOZMIXNGのアカウントを作ってもらったけど、今後はこの方法を使っていこうと思う。

続きを表示する ...

全部セキュア(と思われる)サーバに移動した - Sep 05, 2007

Firefox 3でセキュアじゃない拡張機能はまるっきり使えなくなってしまうということで、須藤さんにお願いしてCOZMIXNGのスペース上にファイル群を移動させてもらった。配布ページからのリンクは変化がないように見えるけど、実際に辿るとリダイレクトでCOZMIXNGのファイルに飛ばすようになってる。あと、現在上がってる拡張機能については全部updateURLを書き換えた。

Firefox 3でのアドオンの自動更新に関する仕様変更(セキュアじゃないアドオンは全部蹴られる件) - Sep 04, 2007

のりさんのところで報じられている、Firefox 3の仕様変更について、チェックインされたパッチを詳しく調べてみた。

今後拡張機能の自動更新では、以下の2つの段階でセキュアかどうかのチェックが入るようだ。

  1. install.rdfに書かれたupdateURL(更新情報を提供するRDFデータソースのURI)がhttpsで始まる、もしくは、updateURLで示された先のRDFデータソースであるXML文書が署名されていてinstall.rdfのupdateKeyに書かれた公開鍵で復号化できること。これが第一の条件。
  2. 更新情報を提供するRDFデータソースに書かれたupdateLink(新バージョンのXPIパッケージのダウンロード用URI)がhttpsで始まる、もしくは、updateHashでハッシュが示されていること。これが第二の条件。

この二つの条件が満たされてやっと、アドオンの自動更新が行われるという仕組みになっている。

よって、アドオン作者が取れる選択肢は以下の4つになる。

  1. install.rdfからupdateURLの記述を削除し、全ての更新をMozilla Add-ons経由で行うようにする。(Mozilla Add-ons経由の更新はhttpsなのでセキュア)
  2. 自前のサーバにSSL証明書を置いてhttpsで通信できるようにして、そこに更新情報提供用のRDFデータソースとXPIパッケージを置く。
  3. 更新情報提供用のRDFデータソースに署名して、さらに、その中にXPIパッケージのハッシュの情報も含めておく。
  4. 自動更新のための仕組みを提供しない。現実は非情である。

やる方として一番楽なのは1と4なんだけど、1には重大な問題がある。

  • 公開申請に通ったアドオンは、ファイルを更新する度に審査があるので、審査待ちで長期間放置されると死亡。
  • 公開申請してないアドオンは、ファイル更新の度の審査は不要だけど、ユーザから見えないから気付かれなくて使ってもらえなくて死亡。

というわけでもうちょっと素早く対応できる路線として2を検討してみようと思ったんだけど、仮にどうにかしてSSL証明書を手に入れたとしても、そもそもさくらのレンタルサーバじゃビジネス用プラン以外ではSSLは使えないんだってさ……

3はやりかた自体が分からない。XMLに署名するとかハッシュ値得るとか、僕の頭ではちんぷんかんぷんです。

ということでいずれにしても今の野良アドオン天国はオシマイだと言えよう。

続きを表示する ...

ハッカーズ その侵入の手口 - Sep 03, 2007

ハッカーズ その侵入の手口 奴らは常識の斜め上を行く(著:ケビン・ミトニック、ウィリアム・サイモン 訳:峯村利哉)Amazon.co.jp

LL魂の物販スペースで買った奴。やっと読み終えた。

投獄経験もあって今はセキュリティ・コンサルタントとして活躍している超有名ハッカーのケビン・ミトニック(と、紹介されてるんだけど僕はこの本を読むまで知らんかった)が、他のハッカーたちに取材して聞いた話を元に書いた、ハッカー列伝的な本。ハッカーたちの体験談が実に真に迫っていて、「見付かるか?! もうおしまいなのか?!」とドキドキハラハラしながら読める、面白い読み物だ。

というエンターテインメント的な側面だけでなく、この本は自分の気を引き締めるために役に立つ一冊だという面もある。技術的な話も多いんだけど、それよりは、ハッカーが人間の思考の隙をどのように突いてくるのかというところに焦点を当てて、各エピソードに解説を加えている。

会社のセキュリティ担当であろうとなかろうと、コンピュータを使う人なら一度は目を通しておいて損はないと思う。

柔軟性の高い開発環境が持つ危険性と、それへの対策 - Mar 24, 2007

プログラムというのはそういうものだで採り上げられているいろんな意味でしゃれにならないApolloという記事は、Firefoxの拡張機能の「危険性」についての話と同じようなことを言っているなと思った。

XPCOMを使えばFirefoxでも確認なしでファイルを削除できる。ファイルの削除時の確認のダイアログは、ExplorerやFinderやNautilusといったシェルが気を利かせて表示してくれているに過ぎない。Windows 98を強制的に再起動させて次の起動時にCドライブをフォーマットする、という拡張機能のサンプルを以前プレゼンで発表したけど、開発環境、特に柔軟性の高い開発環境というのは、そういうこともできてしまう物だということは、理解しておかないといけないんじゃないかと思う。開発者も、利用者も。

携帯でJava製のアプリケーションを実行する時、僕の機種だと、毎回「ネットワークへの接続を許可するか?」と訊ねられる。これは多分パケット通信が発生するから、知らず知らずのうちに大量の通信が発生して料金がヤバいことになるというのを事前に回避するための機構なんだと思うんだけど、こんな風な仕組みを拡張機能やApollo、あるいはOS上で普通に動くアプリケーションに対しても設けていいんじゃないかなー、と思う。

今号のSoftware Designの記事でも書いた、UniversalXPConnect特権を取得するためのコードは、それに近いものがあると思うんだけど、毎回聞かれるか永久に聞かれないかの二者択一というあんまりにあんまりな大雑把さで、ハッキリ言って、実用性の点では激しく疑問だ。Norton Internet Securityを使ってると、アプリケーションの初回起動時や、バイナリが更新されたあとの初回起動時にだけ、そのアプリケーションがインターネットと通信するのを許可するかどうかを訊ねられるんだけど、このくらいの頻度がちょうど良さそうな気がする。

つまり以下のような感じか。

  • アプリケーションなり拡張機能なりは、インストーラ内の情報やあるいは実行ファイルのメタデータに、動作するのに必要な特権の一覧を持っている。
  • 初回起動時、あるいはファイルが更新されたあとの初回起動時にだけ、それらの特権を付与するかどうかの確認が表示される。
  • 特権は、UniversalXPConnectのような「全許可」のようなものではなく、より具体的な、クリティカルな操作に対して個別に用意する。例えば、文字コードの変換やなんかには特権は必要ないだろう。ファイルの読み取り、ファイルの書き込み、インターネットとの通信、パスワードマネージャへのアクセス、Cookieデータベースへのアクセス、他のプロセスの実行、あたりは特権がなければアクセスできないようにしておくべきだろう。

まあ、こんなのただの妄想なんですけどねー。中の人でもなんでもないし、英語もできないし、こんなことBugzillaに提案できやしません……

新生銀行の暗証番号漏洩→不正送金続出? の話のその後の経過 - Mar 20, 2007

先日書いた件のその後についてなんだけど、妙にアクセスされててこのまま放置はまずいかなーと思ったので、その後の経過も書いておく。

  • まず、これは改めて書いておくけども、ニュースソースは件のエントリでリンクしていた「新生銀行」コミュのトピックとトピ主の日記しかなくて、真偽は不明。電話で問い合わせたって窓口で聞いたって、本当であれデマであれ否定されるに決まってるんだから確認の方法は無い。
  • で、そのニュースソースになってたトピックとトピ主の日記なんだけど、今の段階ではどっちもアクセス不能になってる。
    • トピ主の日記はその日のうちにおそらく友人のみ公開くらいに設定されたところまでは僕も見ていたけれど、その後どうなったのかは分からない。アカウント削除までいったのかどうかも不明。
    • 「お金盗まれました」トピック自体は管理者によって削除されている。雑談トピック内にて管理者曰く、トピ主自身からの要請で削除したとのこと。
      • 削除の理由は「警察に口止めされたから」だそうで。

デマの可能性の方が高いような気もするけど、どうなんだろう。

新生銀行で暗証番号漏洩→不正送金続出?(デマの可能性は考慮に入れつつ) - Mar 16, 2007

新生でお金盗まれました(mixi内「新生銀行」コミュ)

この件の続報

15日早朝に数百万円が不正送金される事件の被害に遭った、という人がいるらしい。トピ主の日記のコメントで続報が書かれてるので見てみたところ、トピ主以外にも被害者が大勢出ているという書き込みもあり、事実なのであれば、なんかえらいことになってる雰囲気。

友人曰く、もし本当に複数名が同時に被害に遭うことがあるとしたら、キーロガーとかで暗証番号を盗む手口よりも、SQLインジェクションの脆弱性か何かを突いて一括して暗証番号を抜く手口の方が可能性が高いんじゃないだろうか? ということだったけれども。

っていうかまだどこにもニュースになってないようだし、なんとも言えないなあ。ここ以外に今のところニュースソースが無いので、デマの可能性があるということは考えておいた方がいい。電話で聞いても本当であっても否定するだろうし、デマなら当然否定するだろうし、確認のしようがない。

とはいいつつも、先日新生銀行のサービスについて話を聞いて、3回まで振込手数料無料とか「あ、いいかも」と思ったばかりだったので、この話はちょっとスルーできないです。もし本当だったら口座作るの取りやめにしたいから。

追記。続報書いてみた。

保護された通信と保護されていない通信が混在している時のFirefoxの挙動 - Feb 08, 2007

httpsだと思ってたらhttpで通信することになるような場合、Firefoxはキャンセルできないことが多い

jsonpを僕はよく理解してなかったから改めて調べてみたら、<script/>要素を動的に出力するとドメインの違いを超えて通信ができることを使ったクロスドメイン通信の手法を、jsonpと言うらしいですね。名前が付いてたというのを今の今まで知らなかった。

ここでとり上げられている例を見る限り、これらの局面においてFirefoxがユーザに対して選択の余地を与えていないこと・危険性に対する情報の提示を怠っていることは疑いの余地無しだ。

結論として「IEよりもFirefoxは安全」とはもう言えないと断言されてるけど、これもまた乱暴な言い方だなと思った。セキュリティって、こういうUIの出来不出来だけじゃなく、致命的な脆弱性が発見されてからどれだけ迅速に対処されるかという所も重要な論点で、そういったレイヤの違いを意図的に無視してこうシンプルな言葉で結ぶというのは、Mozillaの「FirefoxはIEより安全ですよ」という無責任に単純化された言い草を非難するための極論、偽悪的振る舞いだとしても、なんだかなあ、と感じる。

とはいえ、結局の所、IEもFirefoxもその他のブラウザもあらゆるソフトウェアも、それぞれ違う所に大きさの異なる穴の開いた船でしかなくて、お互い必死で水をかき出して沈没を防ぎながら、相手の船に開いてる穴の大きさや位置を指指して「あっちにはこんなにでかい穴が開いてる。うちには開いてない!」「いやいや、こっちの穴は大きくても重要じゃない場所だから大丈夫だ、それよりも小さくてもあんなに重要な所に穴が開いてるあっちの方が」と、言い合ってるだけなのかもしれないね。相手の船には穴が開いてない所に、自分の船では穴が開いているとしても。その大きさが大きく違ったとしても。お互いの船に乗っている乗客の数に大きな差があったとしても。

Window Snyderは、インタビュー「セキュリティは,どのように計ればよいのでしょうか」と,逆に質問しますと言ったそうで。

その穴は自分で塞ぐことができるものなのかどうか。自分の側で日常的に気を付けることで穴の影響を受けずに済むことができる種類の穴なのかどうか。そのあたりまで考えて生活することが、本当は必要なのかな。

続きを表示する ...

Page 1/248: 1 2 3 4 5 6 7 8 9 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のコメント

最近のつぶやき