簡単にSOP周辺を理解するページ

このページでは、SOP(Same-Origin Policy)という、ブラウザがWebサイト間を保護する仕組みを説明しています。文章による説明だけでなく、実際にページを操作するデモも含んでいます。できること、できないことを実際に操作してみていくことで、SOPの制限の理解を深めてください。

目次

1. オリジンとSOP

現在のページのURLから、オリジンとSOPについてみていこう。

http://vulnerabledoma.in:80/camp2015_sop/

スキーム + ホスト + ポート の組み合わせを オリジン(Origin)という[1]

スキーム、ホスト、ポートが一致しているリソースの間では、ページの内容を読み出すことやページの文字列の一部を置き換えるといった、さまざまな操作が許される。一方、スキーム、ホスト、ポートのうち1つでも異なっているリソースの間では拒否される。このように、オリジンを境界にデータを保護する仕組みをSOP(Same-Origin Policy/同一生成元ポリシー)という[2]

具体的にどのようなURLのリソースが同じオリジン、異なるオリジンになるかみてみよう。

以下のURLにあるリソースはスキーム、ホスト、ポートが一致しているため、現在のページと同じオリジンにある。

http://vulnerabledoma.in:80/path/index.html
http://vulnerabledoma.in:80/robots.txt

以下のURLにあるリソースはスキーム、ホスト、ポートのいずれかが一致していないため、現在のページとは異なるオリジンにある。(バツ印の部分が不一致)

http://example.com×:80/
http://www.vulnerabledoma.in×:80/
https×://vulnerabledoma.in:443×/

Webの多くの機能で、情報のやりとりの境界にオリジンが使われている。実際に同じオリジンの間で情報をやりとりできる様子、異なるオリジンの間で拒否される様子をみてみよう!

2. SOPの制約を観察する

2-1. フレームに埋め込まれたページを読み出してみる

<iframe>タグを使うと、ページ内に好きな別のページを埋め込むことができる。<iframe>に埋め込まれた同じオリジン、異なるオリジンのページ内容にアクセスできるかを確認し、オリジンの制限を実際にみてみよう。

まずは現在のページと同じオリジンのページへのアクセスを試みる。以下の青いボタンは、同じオリジンのページが埋め込まれたframeAにアクセスするためのJavaScriptを実行するリンクになっている。ボタンを押して、フレーム内のページ内容にアクセスできることを確認しよう。 うまくいけば「これはframeAに書かれたテキスト!」というframeAから取得した文字列がダイアログに表示される。

また、フレーム内にある「埋め込み元にアクセスする」ボタンを押して、フレームからも埋め込んでいる側にアクセスできることを確認しよう。うまくいけば現在のページのHTMLがダイアログに表示される。

alert(frameA.secret.innerHTML)を実行

ちゃんとアクセスできた?

今度は異なるオリジンのページで実験する。次のフレームframeBに埋め込まれたページは、ホストがwww.vulnerabledoma.inなので、現在のページとは異なるオリジンにある。同様に青いボタンを押して、異なるオリジンのframeBのページ内容にアクセスできないことを確認しよう。正しく動けば、アクセスが拒否されたといったエラーメッセージが表示される。

alert(frameB.secret.innerHTML)を実行

ちゃんとアクセスが拒否された?

このように、フレームの内容へのアクセスはオリジンで制限されている。

2-2. 新しく開いたウインドウのページを操作してみる

同じオリジンの間でできることはページを読み出すことだけではない。今度は別ページの内容の変更と、クリック操作ができることをみてみよう。今度はページ内に埋め込んだフレームではなく、新しく開いた同じオリジン、異なるオリジンのウインドウにアクセスできるかを実験しよう。

新しいウインドウに開くページは通販サイトの購入確認画面を模している。通販サイトを模したページ内には、商品の購入個数の入力欄と購入を完了するボタンがあるとし、これらの部分を現在のウインドウから変更/押下することを試みる。

現在のページに書かれた次のようなスクリプトから、同じオリジン、異なるオリジンのページのURLをpageURLの部分に指定し、順に操作を実行していく。

//1
win=window.open(pageURL,'_blank','width=800, height=800');
//2
win.n.value=999;
//3
win.document.getElementsByTagName('input')[1].click();

以下のボタンを順に押して、操作が実行できることを試してみよう。

1. 同じオリジンのページを新しいウインドウに開く 2. 購入個数を999に変更する 3. 購入ボタンを押す

ちゃんと999個の購入が完了した?

次に異なるオリジンのページ(www.vulnerabledoma.in)を新しいウインドウに開き、同様に試してみよう。

1. 異なるオリジンのページを新しいウインドウに開く 2. 購入個数を999に変更する(失敗)

ちゃんとアクセスが拒否された?

このように、フレームの内容へのアクセスと同様、新しいウインドウに開いたページへのアクセスもオリジンで制限されている。また、同じオリジンであれば、ページを読み出すことだけでなく、ページの内容の一部を変更したり、操作をしたりといった様々な操作が許されている。

2-3. エラーメッセージの違いを見る

今度は少し違ったオリジンを境界とする機能をみてみよう。

ページ上でJavaScriptを実行する方法に<script>タグを使用する方法がある。<script>タグのsrc属性にロードしたいスクリプトのURLを指定すると、タグを書いたページのオリジンの権限でスクリプトを実行できる。このとき、ロードしたスクリプトの中に正しく実行できない箇所があるとき、生成されるエラーメッセージはどのようなものになるかに注目してみよう。

「ABC」という文字列だけが書かれたスクリプトをロードして実験する。このスクリプトを実行すると、「ABC」という現在のページに定義されていない変数を参照しようとするためエラーとなる。

現在のページに書かれた次のようなスクリプトから、同じオリジン、異なるオリジンのスクリプトのURLをscriptURLの部分に指定してロードを開始する。

script=document.createElement('script');
script.src=scriptURL;
document.body.appendChild(script);

それぞれ以下のボタンを押してロードを開始し、どのようなエラーメッセージがでるか確認してみよう。

同じオリジンのスクリプトをロードする
異なるオリジンのスクリプトをロードする

同じオリジンのスクリプトの場合は「ABCは定義されていない」といったエラーメッセージが表示される。一方、異なるオリジンの場合は「Script error.」という、エラーの詳細がわからないものになる[3]。小さな違いだが、もし異なるオリジンからロードしたスクリプトからも詳細なエラーメッセージが出てしまっていたら、エラーメッセージの内容から異なるオリジンのページ内容を推測できてしまうことになる。例えば今回の場合は、ページ内に「ABC」が含まれていることがバレてしまうだろう[4][5]

このように、Webの機能の多くはSOPで制限されている。他にもどんな機能がSOPで制限されているか、SOP以外で制限されているものは何か、自分でみてみよう。

3. XSSとSOPの関係

SOPの制限をいくつか具体的に観察したところで、改めてXSSとSOPの関係について考えてみよう。

ここまで、SOPの制限のおかげで、異なるオリジンからは内容の読み取りやページの操作は禁止されるということをみてきた。ところが、XSS脆弱性があった場合はどうだろう。XSS脆弱性があれば、そのオリジンの権限でJavaScriptを実行できる。

このとき、具体的にどのようなことができるだろう?「2-2. 新しく開いたウインドウのページを操作してみる」で、新しいウインドウに開いた同じオリジンのページを操作したことを振り返ってほしい。実験をした時は意図した動作であったが、もし操作を実行する側のスクリプトがXSS脆弱性を使って攻撃者により挿入されたものだとしたらどうだろうか。スクリプトが挿入されたページへターゲットのユーザーを誘導できたなら、水を999個買う処理を攻撃者が勝手に指示できるということになる。

XSS脆弱性があるということは、そのオリジンでできる内容の読み取りやページの操作などを指示する権利の一切を、信頼できない誰かに与えてしまうということだ。そうなれば、そのオリジンのSOPの保護は破壊されたも同然になる。

4. 明示的な許可でオリジンを超える

途中です。 余力がある人は調べてください。

4-1. document.domainを使う

document.domainを互いのページで書き換え、一致させるとオリジンを超えられる。

document.domain='vulnerabledoma.in'を実行 alert(frameC.secret.innerHTML)を実行

4-2. XMLHttpRequest + CORS

CORS(Cross-Origin Resource Sharing)の機能を使うとオリジンを超えられる。


脚注

[1] ブラウザのアドレスバーにはポートの「:80」が含まれていないが、80はhttpの既定のポートのため省略されている。ここではわかりやすくするために「:80」をつけて説明した。

[2] IEでは、オリジンを境界とすることが期待される機能の多くで、ポートが異なっていても、同じオリジンと同様のやりとりができる。(この挙動はWindows 10から搭載されるIEの次期ブラウザ、Edgeでも変わっていない。)

[3] この動作はHTML5の仕様で定められている。 http://www.w3.org/TR/html5/webappapis.html#runtime-script-errors

[4] 実はIEでは少し前まで異なるオリジンのスクリプトでも詳細なエラーメッセージを制御していなかった。IEでは、JavaScriptだけでなくVBScriptの実行もサポートしており、そのエラー内容から、異なるオリジンのページのJSON配列に含まれた内容を読み取れることがはせがわさんと@masa141421356さんに指摘されていた。 http://d.hatena.ne.jp/hasegawayosuke/20130517/p1(はせがわさんのブログ)

[5] もっとも、今回のように「ABC」のような先頭の文字列を取得する程度のことは、工夫をすれば異なるオリジンでもできてしまう。 異なるオリジンのリソースをスクリプトとしてロードし、機密情報を取得する手法は、XSSI(Cross-Site Script Inclusion)などと呼ばれる。 XSSIについては、MBSDの寺田さんの資料が参考になる。 http://www.mbsd.jp/Whitepaper/xssi.pdf


このページは「セキュリティ・キャンプ全国大会2015」の事前学習用に作成したものです。間違いなどあれば、サイボウズLive、@kinugawamasato、 masatokinugawa[at]gmail.comまで。