質問: ### 前提・実現したいこと CSRFの仕組みを理解し、JSONPを利用するときにセキュリティ対策する ### 発生している問題・エラーメッセージ ``` CSRFの仕組みが分からない。 下記2番の「アクセスできる」、というのはログインしたからだと理解できるが、 そのあとの「取得する」という行為が何故できるのかわからない。 スクリプト例などを載せて具体的に仕組みを教えて頂ければ幸いです。 ``` ### 該当のソースコード ``` 1.サイトAにログイン済みの被害者が攻撃者の用意した悪意あるスクリプト(スクリプトBとする)が埋め込まれたWebページにアクセスする。 2.スクリプトBはJavaScriptから機密情報を保持したサービスA(JSONP)にアクセスして、これを取得する。1で被害者はサイトAにログイン済みのため、サービスAには問題なくアクセスできる。←被害者は意図せずサービスAにアクセスしてしまう=CSRF脆弱性! 3.スクリプトBはサービスAから取得した機密情報を攻撃者のサイトに(XmlHttpRequest等を使って)送る。←機密情報の漏えい!! (引用元:https://www.ark-web.jp/blog/archives/2007/01/web20jsonp.html) ```
回答: ヘソクリの在処と金額を管理しているサービス H が `https://hesokuri.example.com/` で展開されているとしましょう。これとは別に統合資産管理サービス S が `https://shisan.example.com/` で展開されています。サービス H はヘソクリ情報にアクセスできる API を JSONP で提供しており、サービス S もこの API を利用し、ヘソクリ以外の資産も含めて統合的に管理できる機能をユーザに提供しています。 A さんは、サービス H とサービス S を両方利用しています。 1. A さんがサービス H にログインし、資産管理ページ、`https://shisan.example.com/kanri` にアクセスしたとします。 2. ここでサイト H は `https://hesokuri.example.com/checkUserStatus?site=shisan` にリダイレクトするよう A さんのブラウザに返します。 3. するとブラウザからサイト H にリクエストが飛びます。 4. サイト H では、`checkUserStatus` が呼ばれたら、クエリストリングの `site` が事前に登録されているものかチェックします。`shisan` は正規の提携先として登録されているのでよしとします。 5. 次にリクエストしてきたユーザがログイン済みかをチェックします。ログイン済みでないならログイン画面を出し、ログインさせます。あと、サービス S がサービス H 内の A さんのヘソクリ情報にアクセスするのを許可するかどうかを聞く画面を出します。ここでは、A さんがログイン状態になり、サービス S にヘソクリ情報を使ってもいいと許可したことにします。 6. サイト H は、A さんが「許可」ボタンを押した後のページとして、HTML を返さず、代わりに `https://shisan.example.com/accepted/` にリダイレクトするよう指示を出します。この元のサービスに戻る URL は、最初の `site=shisan` に紐付くデータとして、サイト H のデータベースに格納されています。 7. ブラウザは `https://shisan.example.comn/accepted` にリダイレクトします。 8. サイト S では、資産管理画面を出します。この画面の html 中に、`` というタグが含まれています。 9. A さんのブラウザがこの html をレンダリングしようとすると、`https://hesokuri.example.com/getHesokuriInfo.js?callback=listHesokuriInfo` へのアクセスが発生します。このリクエストには、ログイン認証済みの証しが cookie に載って飛びます。(JSONP 呼び出し) 10. サイト H は、cookie の情報に基づき、A さんのヘソクリ情報をデータベースから取得し、`listHesokuriInfo([{ place: "冷凍庫の製氷機の下", amount: 150000}]);` を返します。A さんは製氷機の下に 15 万円隠しているようです。もちろん、この関数名は、JSONP 呼び出しのときの callback パラメータで渡ってきた値を利用しています。 11. ブラウザでは返ってきた JavaScript を評価するわけですが、`listHesokuriInfo([{ place: '冷凍庫の製氷機の下', amount: 150000}]);` なので、この関数の実行となります。これは 8. で返ってきた html の中で、JSONP 呼び出しより前に定義されている関数です。サイト S の資産管理画面の該当部分にやってきたデータをはめこんだりします。 まあ、これが JSONP で API を提供しているサイト H と、それを利用したサービスを展開するサイト S の連携です。 で、これを悪用するのがサイト N `https://nusutto.example.com/` です。 * A さんがログイン状態で `https://nusutto.example.com/` を踏んでしまったとしましょう。 * サイト N では画面を html で返しますが、この html 中に、`` というタグが含まれています。 * A さんのブラウザがこの html をレンダリングしようとすると、`https://hesokuri.example.com/getHesokuriInfo.js?callback=send` へのアクセスが発生します。このリクエストには、ログイン認証済みの証しが cookie に載って飛びます。(JSONP 呼び出し) * サイト H は、cookie の情報に基づき、A さんのヘソクリ情報をデータベースから取得し、`sendHesokuriInfo([{ place: "冷凍庫の製氷機の下", amount: 150000}]);` を返します。(CSRF 成功) * ブラウザでは返ってきた JavaScript を評価するわけですが、`sendHesokuriInfo([{ place: '冷凍庫の製氷機の下', amount: 150000}]);` なので、この関数の実行となります。サイト N が用意しているこの関数は、盗んだヘソクリ情報をあれこれ悪用します。 CSRF 攻撃は、ブラウザからのアクセスからは正規のものと非正規のものとが区別できない、ということから可能になってしまいます。CSRF 対策は、けっきょく、正規の一連の流れの中におけるリクエストなのか、を判断するしかありません。一般には、CSRF 対策が必要なページに先立つページでトークンを発行し、WEB アプリ側のセッションに格納しておきます。CSRF 対策が必要なページでトークンがリクエストに乗る仕組みにしておき、リクエストが来たときにそこに乗っているトークンとセッションに格納したトークンが一致すれば正規のリクエストとすることで、CSRF を防ぐことができます。 JSONP はそもそもそういう流れがないクロスドメイン API ですが、先立ってトークンを用意しておいて、あとでリクエスト時に渡されるトークンとの一致で見るしかないかと思います。やりかたはいろいろあると思いますが、たとえば、先の正規の流れの 6. のところでサイト H が返すリダイレクト先を `https://shisan.example.com/accepted/?token=x2032` とすることかと思います。トークンの実際の値は 5. の段階で発行し、セッションにも格納しておきます。で、サイト S が 8. で JSONP を呼び出す際、このトークンを利用し、`` というタグを生成する、と。 かなり適当に書いたので穴はあるかもしれませんが、どうでしょうか。