» Web 開発

Firefox 5 の後方互換性に関わる修正のまとめ

先日 Firefox 5 のベータ版が公開されました。Firefox 5 の正式版は 6/22 のリリースを予定しており、Firefox 4.0.1 のユーザは自動的に更新されます

Firefox 4 以降では Rapid Release プロセスと呼ばれる新しいリリースプロセスに移行し、セキュリティと安定性の改善と共に、新機能や高速化を含めた新しいバージョンを 6 週間毎に公開します (Firefox 6 まではプロセス移行に伴い異なる間隔)。

基本的には後方互換性が維持されるよう開発されていますが、最新 Web 標準への対応や、他のブラウザとの互換性確保のため、細部では後方互換性に関わる変更も含まれています。

そこで、後方互換性に関わる Firefox 5 の主な変更点をまとめてみました。Beta 版で互換性テストをされる Web 開発者の皆様などの参考になれば幸いです。

(さらに…)

文字列からHTMLDocumentを作成するコード

必要に迫られて引数で渡された文字列から HTMLDocument を動的に作成するコードを書いたので紹介します。このコードはchromeコンテキスト上で、つまり拡張上で動作させることを前提としていますのでご注意ください。
下記コードを書く際にmodestで紹介されているnanot_viさんのサイトを参考にしました。またrange.createContextualFragment()実行時のセキュリティ上の課題を克服するためにkazさんのblogを参照させていただきました。

let getDOMHtmlDocument=function(str){
  let doc;
  let range;

  if(document.implementation.createHTMLDocument){
    // Firefox 4.0から
    doc=document.implementation.createHTMLDocument('');
    range=doc.createRange();
    range.selectNodeContents(doc.documentElement);
    range.deleteContents();
    doc.documentElement.appendChild(range.createContextualFragment(str));
  }else{
    // Firefox 3.6.xまで
    let doctype=document.implementation.createDocumentType(
      'html',
      '-//W3C//DTD HTML 4.01 Transitional//EN',
      'http://www.w3.org/TR/html4/loose.dtd'
    );
    doc=document.implementation.createDocument(null,'html',doctype);
    range=doc.createRange();
    range.selectNodeContents(doc.documentElement);
    let content=doc.adoptNode(range.createContextualFragment(str));
    doc.documentElement.appendChild(content);
  }
  return doc;
};

上記コードの5行目から11行目までのif文のブロックはcreateHTMLDocumentメソッドに対応しているFirefox 4.0以降用です。createHTMLDocumentメソッドに対応していないFirefox 3.6.xまでで動作するのは12行目から23行目までのelseブロックのコードとなります。

私はnanot_viさんのサイトを元にこのコードを書きはじめたのですが、createContextualFragmentメソッドの呼び出しのタイミングで必ず例外がスローされるという状況で行き詰まってしまいました。
HTMLのテキストが正規化されていないためパースできないのか、と最初は考えていたのですがcreateHTMLDocumentメソッドを使っているコードでも例外がスローされることに変りはないので数日悩むことになりました。
そしてたまたまkazさんのblogの記事を拝見することとなり、セキュリティ的に鑑みてselectNodeContentsメソッドの引数にブラウザ由来のdocument.documentElementではなくユーザが作成したdocumentオブジェクト由来のdoc.documentElementを渡すべきということを知り、そのような修正を加えたところ例外が発生しなくなり、例外発生の問題は解決されました。もちろん引数で渡された文字列も問題なくDOM Objectとなりました。

ちなみにブラウザ由来のdocumnetオブジェクトを使うとどのようなセキュリティ上のリスクがあるのかはkazさんのblogを参照してください。ブラウザ由来のdocumentオブジェクトは、chromeコンテキスト上に存在することがポイントです。
私は上記コードの開発をFirefox 3.6.xとFirefox 4.0pre ( Minefield ) で行っていました。しかしnanot_viさんのサイトにはFirefoxでの動作報告も含まれています。これはつまるところセキュリティ上の理由により、どこかのタイミングでFirefoxの仕様が変更され例外がスローされるようになったのだと推察します。

JavaScript のブロックスコープと名前空間

Web 開発や拡張機能開発で JavaScript のコードを書いていると、誰もが一度は次のようなことで悩むかと思います。

  • ブロックスコープと名前空間 (グローバル変数汚染の回避)
  • 読み書きしやすくデバッグしやすいコードスタイル
  • コールバック関数と this オブジェクトの取り扱い
  • デバッグ方法とデバッグ支援モジュール
  • 非同期処理の書き方

いずれも解決方法は人によって様々で、これが常にベストと言えるものがなさそうですが、私なりにそれぞれ検討したことなどを書いてみようかと思います。もっと良い方法があるとか色々皆さんのご意見やツッコミをいただければ幸いです。

ブロックスコープと名前空間

JavaScript では名前空間は言語仕様でサポートされておらず、ライブラリや拡張機能などのコードを書くときにはグローバル変数の使用を最小限に抑える必要があります。先日の Mozilla 勉強会@東京 3rd でも佐藤さんと守山さんの発表で話題になっていましたが、今回はまずブロックスコープ周りのいろいろなコーディングスタイルを比較検討してみようかと思います。

(さらに…)

CSS によるブラウザ履歴の漏えいを防ぐ取り組み

これは、Mozilla Security Blog の記事 Plugging the CSS History Leak (英文) の抄訳です。Web 開発者の方は Mozilla Hacks の記事抄訳 CSS の :visited に行われるプライバシー対策 も参照してください。

プライバシーの保護は必ずしも簡単なことではありません

Mozilla では近く、以前からブラウザ各社が取り組んでいる個人情報漏えい問題の対策を Firefox の開発ツリーに追加します。私たちはこの改善を非常に楽しみにしており、他のブラウザも後に続いてくれることを期待しています。しかし、これは解決が難しい問題であるため、Mozilla がなぜこのようなアプローチを取ることにしたのか説明しておきたいと思います。

履歴の取得

Web ページ上のリンクは、ユーザがそのリンク先を訪れたことがあるかどうかによって見た目が変わる場合があります。既にご存知かもしれませんが、通常、訪問済みリンクは青色から紫色に変わります。これは、Web デザイナーが優れた Web サイトを作成できるよう用意されている多くの機能のひとつに過ぎず、たいていの場合は良心的に利用されています。

問題は、リンクを表示しているページがその見た目を判別でき、ユーザがどのリンク先を訪れたことがあるかを知る手がかりとなってしまうということです。つまり、ユーザだけでなく Web サイトも、ユーザの Web サイト閲覧履歴を知ることができてしまうのです。

元々 Web の便利な機能のひとつとして定義された訪問済みリンクのスタイル付けは、長いこと Web の一部として利用されてきました。つまりこれは非常に古い問題で、これまでも時折論争を巻き起こしています。

最も単純な修正を行うとすれば、未訪問リンクと訪問済みリンクのスタイルの違いをなくしてしまうことですが、これでは利便性が損なわれます。Web サイトだけでなくユーザ自身も、どのリンク先を訪れたことがあるか分からなくなってしまいます。そこで Mozilla では、開発者の David Baron が Web サイトへの影響を最小限に抑えつつユーザのプライバシーを守る方法 を考案し、ユーザ保護の観点から Firefox に組み込むことにしました。私たちはこれがこの問題に対する最善の解決策であると考えており、他のブラウザも同様の取り組みを行ってもらえれば嬉しく思います。

技術的な詳細

ここでの最大の脅威は、高い処理能力を備えた技術、つまりユーザのブラウザからすばやく大量の情報を抜き出すプログラムです。そうした技術は、非常に集中した攻撃だけでなく、一般的に、様々な攻撃者にとってより有効な、広範な「総当たり攻撃」を可能にすることから、特に憂慮すべきです。(潜在的にはフィンガープリントの採取も含まれます)

JavaScript の getComputedStyle() 関数とそれに関連した機能は高速であり、1 分間に何十万件ものリンクを訪問済みかどうか推測するのに利用できます。Web の仕組みに大幅な変更を加えることなく、ユーザの訪問済みリンクを Web サイトから取得されないようにするため、以下のような 3 つの極めて巧妙な方法でリンクのスタイル付けを行うアプローチを取ることにしました。

変更点 1: レイアウトに基づいた攻撃

まず、訪問済みリンクを未訪問リンクと区別するのに使用できるスタイルの種類を制限します。具体的には、訪問済みリンクは、文字、背景、アウトライン、ボーダー、SVG の線と塗りといった、配色のみ変えられるようにします。その他のスタイルの変更はいずれも、背景画像を読み込んだりページ上のスタイル付けされたコンテンツの位置やサイズを変えたりすることで、リンクが訪問済みかどうかを分かるようにするものですが、これらは訪問済みリンクを判別し特定するのに利用されるおそれがあります。

私たちが変更できることはカスケーディングスタイルシート (CSS) の許容範囲になりますが、CSS 2.1 の仕様書は訪問済みリンクの悪用を考慮しています。

「そのため、ユーザエージェントはすべてのリンクを未訪問リンクとして扱うか、訪問済みと未訪問リンクを異なるように表示しつつ、ユーザのプライバシーを守るその他の方法を実装することができます。」(CSS 2 仕様書 より引用)

変更点 2: 一部のタイミング攻撃

次に、Gecko レンダリングエンジンの実装にいくつかの変更を加え、訪問済みと未訪問リンクの表示にかかる時間の違いを最小限にするため、処理プロセスを均一化します。この変更により、訪問済みと未訪問リンクの両方について、すべてのスタイルがすべてのリンク上で制御され、保存されるようになります。その結果、リンクがスタイル付けされるときに、適切なスタイルが選ばれ、訪問済みと未訪問リンクのコードパスが原則として同じ長さとなります。これによって、一部の展開が容易なタイミング攻撃を排除できます。

変更点 3: 算出スタイルを利用した攻撃

JavaScript による、実際に適用されているスタイルの取得が制限されます。Web ページがリンク (とその子孫要素) の算出スタイルを取得しようとすると、Firefox は未訪問リンクのスタイル定義を返すようになります。

これがユーザにとってどのような意味を持つか

ほとんどのユーザは、Web サイトの表示が変わったことには気付かないでしょう。ごく一部の Web サイトでは、見た目が少し変わる可能性もありますが、訪問済みリンクはこれまで通り異なる色で表示されます。また、配色以外のスタイルで訪問済みリンクを区別している一部のサイトでは、上記の変更に対応するまで、当初は若干見た目が崩れる可能性もありますが、それはユーザのプライバシーを守るために正しいトレードオフであると私たちは考えます。これは、厄介で非常に作り込まれた攻撃手法なのです。私たちは Web のいかなる部分も壊したくありませんが、可能な限り攻撃を阻止する必要があります。

もっとも、私たちは現実に目を向けなければなりません。すべてのブラウザは、個人情報の漏えい原因となる様々な機能を実装しており、CSS による履歴の取得を制限しても、これらの漏えいをすべて防げるわけではありません。しかし、どのような方法を使っても、この最も恐ろしく最も効果的な履歴の取得を阻止することは、ユーザのプライバシー保護に大きなメリットをもたらすことから、重要なことであると私たちは信じています。

他の攻撃が心配であったり、この問題の修正版が手に入るのを待てないときは、バージョン 3.5 以降の Firefox に実装済みの、訪問済みリンクのスタイル付けを完全に無効にする (この手の攻撃を今すぐ阻止する) 方法をお試しください。設定エディタ about:config を開いて、layout.css.visited_links_enabled の設定値を false に変更すれば完了です。これにより履歴の漏えいを防ぐことはできますが、どの Web サイトでも訪問済みリンクを見分けられなくなることに注意してください。

Web 上でのプライバシーを高めるために

私たちは、ユーザのプライバシー保護に対する期待と、Web 上で実際に起きていることのギャップを埋めたいと考えています。ブラウザが確実にプライバシーを守ってくれると期待するユーザもいるので、可能であればそれに応えたいと思います。しかしながら、プライバシー保護というものは、ブラウザに単純に追加できる機能ではありません。時には利便性を犠牲にすることもあるでしょう。私たちは、ユーザにより安全な Web 体験を提供しつつ、Web デザイナーにとって柔軟性のバランスを取れる修正方法を見つけたと考えています。