2016年4月28日木曜日

[CVE-2015-7214] data および view-source URI を通じたクロスサイト読み取り攻撃 (write-up)

詳細

MFSA 2015-149: data および view-source URI を通じたクロスサイト読み取り攻撃

PoC (Mozillaに公開の許可を確認済み)

https://github.com/llamakko/CVE-2015-7214

  • www/index.html
    • Web上のSOP(Same-Origin Policy)バイパスのPoC
  • local/index.html
    • ローカル上のSOPバイパスのPoC

解説

この脆弱性を発見するきっかけとなった挙動から順に解説していきます。

なお、この解説は www/index.html にあるPoCの内容が基点となっています。


Firefox 42.0のe10s環境では、http(https)なURL上で location.protocol を用いてスキームを data に変更するとアドレスバーの表示は変わらずにブランクページに遷移する挙動が存在しました。

この挙動では、スキームを変更後も変更前のオリジンからDOMの操作が可能でした。

僕はこの挙動をアドレスバー偽装のトリガーに使えるかもしれないと考え、開発者ツールで検証を行ってみたところ、以下のような挙動を発見しました。

  1. https://example.com にアクセス
  2. location.protocol="data"
  3. location.host="www.google.co.jp"
  4. location.protocol="https"
  5. history.back()

上記の操作を開発者ツール上で行うと、アドレスバーの表示は https://www.google.co.jp で、ページにはオリジンが https://example.com のブランクページが表示されている状態となります。

これはアドレスバー偽装が成功しているように思えますが、このままではWebページ上のみの操作で再現することができず、もしそれが可能になる方法を発見したとしてもブランクページが表示されるわけなのでユーザに対して攻撃することができない、と考え、まずは location.protocol="data" を行ったあとのページに偽装したいページと同様の表示をさせる方法を探すことにしました。

この挙動の再現方法は嬉しいことに data URL Scheme を用いているため、 data:text/html,[任意のHTML] という形にできればページの偽装も成功させることが可能と考えました。

そこで、まずは以下の方法を試してみます。

location.protocol="data"
location.host="text/html,"

上記の方法では location.host の中に /, が含まれているため、期待している形通りにURLを変更することができませんでした。

他に良い方法がないか考えてみたところ、以下の方法を使えば location.host に上記のような記号を含めずに先ほどの形の表現が可能だと考えました。

location.protocol="data"
location.host="text"
location.pathname="html,foo"

しかし上記の方法ではダウンロード用のダイアログが表示されてしまい、ページはブランクページの状態から変更ができませんでした。

その後 location.host の制約が面倒だと感じたため制約の抜け道を探してみたところ、 blob URI Scheme 内で location.protocol="data" を行ったときは location.host などのLocationオブジェクトのプロパティが参照する箇所が以下のようになることを発見しました。

blob:http://example.com/ff11ec98-c4ce-4ace-b66d-e6ccaf69bdfe
↓ location.protocol="data"
data:http://example.com/ff11ec98-c4ce-4ace-b66d-e6ccaf69bdfe

<location.protocol>:<location.pathname>

このとき、 data の箇所を location.protocolhttp://example.com/ff11ec98-c4ce-4ace-b66d-e6ccaf69bdfe の箇所を location.pathnameでそれぞれ変更することができます。

なお、この場合では location.host の変更はできなくなりますが、今までは location.host で変更していた箇所を location.host と比べて制約が非常に少ない location.pathname で変更することが可能になったため、問題であった location.host の制約からは抜けられることになります。

このあたりからこれらの挙動はアドレスバー偽装より重要度の高い脆弱性のトリガーになり得るのではないかと思い、別の目線で検証をしてみることにしました。

その中で見つけた挙動が以下の挙動です。

blob:http://example.com/ff11ec98-c4ce-4ace-b66d-e6ccaf69bdfe
↓ location.protocol="data"
data:http://example.com/ff11ec98-c4ce-4ace-b66d-e6ccaf69bdfe
↓ location.protocol="view-source"
操作しているタブがクラッシュ(e10s環境ではない場合はFirefox自体がクラッシュ)

普段ならバグハントでクラッシュが起きることは嬉しい現象なのですが、今回のケースでは更なる検証を阻害する邪魔な要素でしかありません。

そのためなんとかしてクラッシュを回避できないか試してみたところ、以下のように view-sourceview-source を参照することでクラッシュの回避が可能なことを発見しました。

blob:http://example.com/ff11ec98-c4ce-4ace-b66d-e6ccaf69bdfe
↓ location.protocol="data"
data:http://example.com/ff11ec98-c4ce-4ace-b66d-e6ccaf69bdfe
↓ location.pathname="view-source:http://example.com/ff11ec98-c4ce-4ace-b66d-e6ccaf69bdfe"
data:view-source:http://example.com/ff11ec98-c4ce-4ace-b66d-e6ccaf69bdfe
↓ location.protocol="view-source"
クラッシュはせずにview-sourceの内容が表示される

さらに、この手法と window.open を用いて子ウィンドウに対して親ウィンドウのドメインとは全く関係のないドメインのview-sourceを参照させようとしたケースでも、正常にそのドメインのview-sourceの内容が表示されることを確認できました。

ここで試しに全く関係のないドメインのview-sourceを参照している子ウィンドウのDOMに対して親ウィンドウからアクセスを試みたところ、なんとアクセスが成功してしまい、結果、今までの挙動をSOPバイパスの脆弱性へと結びつけることに成功しました。

後日談ですが、アドレスバー偽装に使えそうな挙動に関しては最終的にページを偽装する方法を見つけることができなかったのですが、のちに偽装させる方法を発見することができ、今回の脆弱性の修正後に別途Mozillaに報告をして脆弱性として認定されました。

以下は、その脆弱性が修正されたときに公開された情報です。

MFSA 2016-28: 履歴遷移と Location プロトコルプロパティを通じたロケーションバー偽装

また、この脆弱性の修正後に別の手法を用いることで同じような挙動を再現できることに気づき、あとはDOMへのアクセスを成功させるだけで同様の脆弱性へと結びつけられる、という段階までいっていたのですが、モチベが続かずにしばらく放置していたところ、いつの間にかその手法に使っていた挙動が防がれていました…。