2015年9月30日水曜日

色んなリダイレクト。

最近リダイレクトについて色々試していたので、軽くまとめてみたいと思います。
そもそもなぜリダイレクトに注目したのかというと、リダイレクトはSOPの制約を受けないからです。詳しくはこちらをご覧下さい。しかしリダイレクトに一切制約がないかというと、そうでもありません。制約については、Chromeの以下のエラーがうまく説明してくれてます。

Unsafe JavaScript attempt to initiate navigation for frame with URL 'http://example.com' from frame with URL 'http://evil.com'. The frame attempting navigation is neither same-origin with the target, nor is it the target's parent or opener.

つまり、リダイレクトを指示したはフレーム(もしくはウィンドウ)は、リダイレクトされるフレーム(もしくはウィンドウ)と以下の様な関係でなくてはなりません。
  1. 同一オリジンである。
  2. 親フレームである。
  3. Openerウィンドウである。
同一オリジンは当たり前なので、2番と3番を見ていきます。
2番は、親フレームが子フレームをリダイレクトすることができるという意味です。スクリプトでいうと以下のような感じです。
document.frames[0].location.replace(サイト名);

3番は、新しいウィンドウを以下のようなスクリプトで開いた場合、新しいウィンドウをリダイレクトできるという意味です。
var tab = window.open(サイト名);
setTimeout(function() { tab.location.replace(サイト名); }, 3000);

このようにリダイレクトはオリジンを気にせず、色んなフレームをリダイレクトできます。


リダイレクト悪用編Only for educational purposes


ここからはリダイレクトの悪用について考えていきます。
実は、2番と3番は逆の使い方もできます。2番の逆は、子フレームが自らのTopフレームや親フレームをリダイレクトすることができるという意味です。

PoC

この最も簡単な悪用方法が広告です。広告はiframeで表示されることが多く、スクリプトタグを禁止していたとしても、その他のon~的なイベントを使ってスクリプトを混入できる場合があります。スクリプトが混入できる場合は広告が表示されるページを上記のようなスクリプトで任意のサイトにリダイレクトすることができます。

例えばCybozu.netの広告にこの脆弱性があった場合、Cybozu.netにアクセスする全てのユーザーが悪意のあるサイトにリダイレクトされます。それだけではなく、Cybozu.netをiframe内に表示してるサイトのユーザーも悪意のあるサイトにリダイレクトされます。


対策
信頼できないサイトをiframe内に表示する場合はsandbox属性をつけましょう。広告の場合はユーザーが広告をクリックしたらそのサイトに飛ばすという作業が必要な為、sandboxのallow-popupsを使いましょう。しかし、allow-popupsは新しく開いたタブもsandbox化されてしまうという難点があります。これがどうしても嫌な場合はallow-popups-to-escape-sandboxのリリースを待ちましょう。allow-top-navigationは必要ない限り入れないようにしましょう。



さて次は3番の逆を見ていきます。これは新しいウィンドウで開けられたサイトが、window.openerを使って元のウィンドウをリダイレクトできるという意味です。openerの特徴は、サイトがどれだけリダイレクトしてもopenerの関係が継続されるということです。

PoC

上記のサイトに行きリンクをクリックすると新しいタブが開きます。リンクがあったサイトは一瞬Yahooにリダイレクトしますが、すぐにGoogleにリダイレクトされます。スクリプトを見てみましょう。

リンクがあるサイト(サイトA)
function go(){
window.open("http://shhnjk.hatenablog.com/entry/2015/08/04/185205"); window.location.replace("http://www.yahoo.co.jp/");
}

新しいタブで開いたサイト(サイトB)
setTimeout(function(){ opener.location.replace("https://www.google.com"); }, 1000);

サイトAのリンクがクリックされるとgo()が発動します。まずwindow.openでサイトBを開きます。この時点でサイトAがサイトBの”opener”になります。その後サイトAは自らをYahooにリダイレクトします。しかしopenerの関係はoriginが変わっても継続する為、サイトBがopener.location.replaceを使ってサイトAをYahooからGoogleにリダイレクトすることが出来ます。

こんな感じでopenerはかなり使えます。更にopenerにはもう一つ特徴があります。それはtarget="_blank"が指定されているタグを使うことにより、openerの関係を作れるということです。

PoC

Facebookのリンクをクリックすると先ほどと同じサイトBが新しいタブで開かれます。Facebookのリンクにはtarget="_blank"が指定されている為、openerの関係ができます。後は先ほどと同じサイトBのスクリプトにより、FacebookがGoogleにリダイレクトされます。

この様にタブやウィンドウを使ってサイトをリダイレクトするテクニックをTabnabbingといいます。
気付いた方もいると思いますが、今回紹介したTabnabbingはIEでは動きません。これはIEがopener.location.replace()に対してopenerのlocationを変えるのではなく新しいタブで開くという挙動をする為です。なので、クロスオリジンからのポップアップとみなされ、ポップアップブロッカーによって拒否されます。


対策
リンクにtarget="_blank"を指定する場合はrel="noreferrer"も指定しましょう。こうすることで、openerの関係が出来なくなります。しかし、全てのtarget="_blank"がrel="noreferrer"によって解決する訳ではありません。こちらに詳しい例が記載されています。rel="noreferrer"が意味をなさない、もしくはwindow.openなどでopenerの関係を作りたくない場合はwindow.opener.__proto__ = null;とすることでwindow.openerをnullにできます。


ユーザーが出来る対策
target="_blank"が指定されているのにrel="noreferrer"が指定されていないリンクはFacebookなどの有名なサイトにもあります。ですが、自分がクリックする全てのリンクをチェックするわけにもいきません。なのでリンクをクリックする際は右クリックをして”新しいタブで開く”をクリックすることをお薦めします。これによりtarget="_blank"になっていてもopenerの関係ができることはありません。しかし、対策はwindow.openなどのスクリプトによってopenerの関係ができるものには、効果がありませんので注意して下さい。



公開に至った経緯
Facebookのバグについては、1ヶ月以上前にFacebookに報告しましたが、既知の問題と言われました。そもそもFacebookはopen redirectを脆弱性と認めていない為、このバグは脆弱性と認められないのだと思います。GoogleもTabnabbingを脆弱性と認めていません(PoC)。更にTabnabbingは少なくとも5年前から知られているテクニックです。その為、今回Tabnabbingの実例を知ってもらい、対策を付け加えることで、リンクをクリックすることで何が起こりうるのかを知ってもらうのが最善策ではないかと考えました。

また、リダイレクトを指示するサイトとして、はてなブログを使わせてもらいました。これはFacebookで皆さんが良くクリックするのが、はてなブログのリンクなのではと思ったのと、はてなブログなど、沢山のサイトではスクリプトが使えるということを知って欲しかったからです。"スクリプトが使える"もしくは"XSSがある"という怖さについては、来月更に掘り下げて行こうと思います。


ではでは。