質問: PHP言語の参考書を読んでいて、CSRF対策の部分で、分からない部分があったので教えて頂ければ幸いです。 予めハッシュ値のワンタイムトークンをサーバ側セッションと、のち送信されるだろうhidden項目に保存し、クライアントから送信された際にそれら比較を行う、という手続きと解釈しています。 その説明部分に、「より厳密にはトークンを隠しフィールドだけではなく、クッキーにも保存しますが、ここでは簡略化のために割愛しています。」 との記載があります。 クライアント側のクッキーにも保存してWチェックをすべき、ということなのでしょうが、 それは、セッションとクッキーの比較を行うべきなのでしょうか?それともhiddenの項目内容とクッキーの内容を比較すべきなのでしょうか?背景が分かっていなくて妙なことを聞いてすみません...
回答: CSRF対策を正しく理解するためには、まずは偽装ではない普通のリクエストがどのように処理されるのかを理解していないといけません。 ``` ユーザ・ブラウザ 銀行サイト・サーバ ---------------------------- ---------------------------------- [1] 銀行サイトの ログインページにアクセス → ログインページのHTMLを作成 ↓ [2] ブラウザがログインページを ← 作成したログインページを応答として返す 表示 ↓ [3] ユーザがログイン情報を 入力してログインボタンを クリック ↓ [4] ブラウザがログイン情報を送信 → ログイン情報をチェックし、OKなら サーバ内のセッション情報にログイン状態で あることを記録 [セッション情報] セッションID: xxxx987 ユーザID: yyyy ↓ Cookie情報にセッションIDを入れて [5] ブラウザがCookieを記録し、 ← ログイン完了ページを応答として返す ログイン完了ページを表示 [Cookie] 銀行サイト用 セッションID: xxxx987 ↓ [6] ユーザがオンライン送金の ページに移動 ↓ [7] ブラウザが銀行サイト用の Cookieと共に リクエストを送信 → 送られてきたCookie内のセッションIDから ユーザとログイン状態であることを確認 ↓ [8] ブラウザが ← オンライン送金ページを応答として返す オンライン送金ページを表示 ↓ [9] ユーザが送金情報を入力して 送金ボタンをクリック ↓ [10] ブラウザが送金情報と 銀行サイト用のCookieを送信 → 送られてきたCookie内のセッションIDから ユーザとログイン状態であることを確認 送金情報に従ってユーザの銀行口座から 送金先に送金処理を行う 【送金処理完了!!!】 ``` (典型的なオンライン送金の画面だと、あれこれ確認画面が入るかと思いますが、そこらへんは省略します。) 典型的な(CSRFと命名された時の)偽装方法では、ユーザが[5]の状態まで進んだところで、何らかの技法を使って他サイトへ誘導します。 ``` ユーザ・ブラウザ 銀行サイト・サーバ 他サイト・偽サーバ ---------------------------- ---------------------------------- ---------------- : : ↓ Cookie情報にセッションIDを入れて [5] ブラウザがCookiを記録し、 ← ログイン完了ページを応答として返す ログイン完了ページを表示 [Cookie] 銀行サイト用 セッションID: xxxx987 ↓ [6f] 何らかの方法でユーザを 他サイトに誘導する(*1) ↓ [7f] ブラウザが他サイトへの リクエストを送信 → 怪しいJavaScriptなどを仕込んだページを (銀行サイト用Cookieは HTMLとして作成 送信されない) ↓ [8f] ブラウザが ← オンライン送金ページを応答として返す 怪しいページを表示するとともに JavaScriptを実行 ↓ [9f] JavaScriptが送金情報を 偽装して送金と同じリクエストを 送信しようとする ↓ [10f] ブラウザが送金情報と 銀行サイト用のCookieを送信 → 送られてきたCookie内のセッションIDから ユーザとログイン状態であることを確認 送金情報に従ってユーザの銀行口座から 送金先に送金処理を行う 【送金処理完了!!!】 ``` (*1) XSSを併用したり、(銀行サイトなんかではないでしょうが、入稿チェックのゆるいサイトなら)広告記事なんかが利用されたりすることもあります。 ([6f]〜[10f]なんかも概念図です。悪いことをする人たちは研究熱心で色々なテクニックを研究しているものです。JavaScriptは使わず、無害そうなリンクを踏ませる、なんて場合もあります。) と言うわけで、[10]のサーバ側処理がゆるゆるだと他サイトが偽のリクエストを送っても、送金処理などが行われてしまうわけです。そこで[10]の処理で確認方法を強化するための対策が通常の書籍等で解説されているCSRF対策になります。 ちなみにCSRFのCross Siteは、このように他サイトに誘導することを想定した命名ですが、現在では拡大解釈してリクエストの偽装一般を広義のCSRFと見ることが多いです。 で、対策を施した時の流れがこんな感じ。 ``` ユーザ・ブラウザ 銀行サイト・サーバ ---------------------------- ---------------------------------- : : ↓ Cookie情報にセッションIDを入れて [5] ブラウザがCookiを記録し、 ← ログイン完了ページを応答として返す ログイン完了ページを表示 [Cookie] 銀行サイト用 セッションID: xxxx987 ↓ [6] ユーザがオンライン送金の ページに移動 ↓ [7a] ブラウザが銀行サイト用の Cookieと共に リクエストを送信 → 送られてきたCookie内のセッションIDから ユーザとログイン状態であることを確認 CSRFトークン(nonce)作成し、 セッションに格納すると同時に、hiddenで HTMLに加工したトークンを入れておく [セッション情報] セッションID: xxxx987 ユーザID: yyyy トークン: zzzz ↓ [8a] ブラウザがhiddenで含む ← 加工したトークンをhiddenで含む オンライン送金ページを表示 オンライン送金ページを応答として返す ↓ [9a] ユーザが送金情報を入力して 送金ボタンをクリック ↓ [10a] ブラウザが送金情報と hidden内の加工トークンと 銀行サイト用のCookieを送信 → 送られてきたCookie内のセッションIDから ユーザとログイン状態であることを確認 ☆さらに、セッション内のトークンを加工し ブラウザからhidden項目として送られてきた トークンと一致するか確認する☆(より安全!) (一致が確認されたトークンはすぐに削除) 送金情報に従ってユーザの銀行口座から 送金先に送金処理を行う 【送金処理完了!!!】 ``` ただし、hidden情報と言うのはHTMLソース内に丸見えになっているので、色々な経路で流出が懸念されるため、さらに厳しいチェックをしたいという場合もあるでしょう。当然HTMLのソースを見てもCookieの中身は見えないので、Cookieを併用した方がもっと安全じゃないか、と言われるわけです。 ``` ユーザ・ブラウザ 銀行サイト・サーバ ---------------------------- ---------------------------------- : : ↓ [7b] ブラウザが銀行サイト用の Cookieと共に リクエストを送信 → 送られてきたCookie内のセッションIDから ユーザとログイン状態であることを確認 CSRFトークン(nonce)作成し、 セッションに格納すると同時に、hiddenで HTMLに加工したトークンを入れておく さらにCookieにも別の方法で加工された トークンを仕込む [セッション情報] セッションID: xxxx987 ユーザID: yyyy トークン: zzzz ↓ 加工したトークンをhiddenで含み [8b] ブラウザがhiddenで含む ← 別の方法で加工されたトークンをCookieに入れて オンライン送金ページを表示 オンライン送金ページを応答として返す [Cookie] 銀行サイト用 セッションID: xxxx987 加工2トークン: ?!*/+@ ↓ [9b] ユーザが送金情報を入力して 送金ボタンをクリック ↓ [10b] ブラウザが送金情報と hidden内の加工トークンと 銀行サイト用のCookieを送信 → 送られてきたCookie内のセッションIDから ユーザとログイン状態であることを確認 ☆さらに、セッション内のトークンを加工し ブラウザからhidden項目として送られてきた トークンと一致するか確認する☆(より安全!) ☆さらにさらに、セッション内のトークンを 加工法2で加工し、ブラウザからCookieとして 送られてきたトークンと一致する か確認する☆(よりもっと安全!) (一致が確認されたトークンはすぐに削除) 送金情報に従ってユーザの銀行口座から 送金先に送金処理を行う 【送金処理完了!!!】 ``` この例では同じトークンを加工法だけ変えてhiddenとCookieの両方に入れるように書いていますが、トークンを2種類用意してhidden用とCookie用では生成・破棄のタイミングを変えるなんてことも行われます。 ちなみに図をよく見ればわかるのですが、ブラウザはリクエストを送信する時に相手だけ見て自動的にCookieを送信しちゃいますから、ある種の攻撃(例えば[8b]まで進んだ状態でXSSが成功しちゃってるとか)に対しては意味がないです。対策を併用する時はその対策でどんな攻撃を防げるのか、しっかり意識しておかないと、コストがかかるばかりで効果はイマイチと言う場合もあります。 この辺りの細部までは、IPAのセキュアプログラミングガイドなんて読んでもはっきりとは書いていないかもしれません。(IPA セキュア・プログラミング講座, 安全なウェブサイトの作り方) 銀行・公的機関・個人情報を扱うなど、特に高いセキュリティが要求されるサイトでは、重要な処理のたびにパスワードとは異なる暗証番号(ま、もう一度パスワードでも良いですが)を入力させたり、ハードウェアのトークンジェネレータを顧客側に持たせたりしているのはご存知だと思います。 Refererチェックも単純ではありますが、それなりの効果があります。併用した方が良いでしょう。 それは本に書いてるからわかってるんだ、と言うことを長々と説明してしまったかもしれません。 本題に戻ると ***クライアント側のクッキーにも保存してWチェックをすべき、ということなのでしょうが、 それは、セッションとクッキーの比較を行うべきなのでしょうか?それともhiddenの項目内容とクッキーの内容を比較すべきなのでしょうか?*** * セッション内の情報とhiddenからの情報 * セッション内の情報とクッキーからの情報 と言う形でのダブルチェックを行うべきと言うことになります。