開発者のための正しいCSRF対策

著者: 金床 <anvil@jumperz.net>
http://www.jumperz.net/

■はじめに

ウェブアプリケーション開発者の立場から見たCSRF対策について、さまざまな情報が入り乱れている。筆者が2006年3月の時点において国内のウェブサ イトやコンピュータ書籍・雑誌などでCSRF対策について書かれている記事を調べた結果、おどろくべきことに、そのほとんどが誤りを含んでいたり、現実的 には使用できない方法を紹介したりしていた。そこで本稿ではウェブアプリケーション開発者にとっての本当に正しいCSRF対策についてまとめることとす る。また、採用すべきでないCSRF対策とその理由も合わせて紹介する。

■あらゆる機能がターゲットとなりうる

ウェブアプリケーションの持つ全ての機能がCSRF攻撃の対象となりうる。まずこのことを認識しておく必要がある。

AmazonのようなショッピングサイトやGoogleのような検索サイト、そしてはてなダイアリーやMixiのような日記やブログを書くサイトなど、 ウェブアプリケーションにはさまざまなものが存在する。そしてそれぞれのアプリケーションは細かい「機能」の積み重ねによって成り立っている。

例えばショッピングサイトは、以下のような機能の集まりである。

  • ログインする
  • 商品を検索する
  • 商品の情報を表示する
  • 商品をカートに入れる
  • 送り先の住所やクレジットカードの番号などを入力する
  • 確認のため注文内容を表示する
  • 精算を決定する
  • ログアウトする

  • 現実のケースとしてCSRF攻撃の対象になるのは「商品をカートに入れる」などの「攻撃者にとってうまみのある機能」であることが多いが、理論的にはアプ リケーションに存在する機能は全てCSRF攻撃の対象となりうる。CSRF対策を考える上でまず行うべきことは「アプリケーションのうち、どの機能につい てCSRF対策を行うか」を決定することである。全ての機能について対策を施してもよいし、重要と思われるいくつかの機能だけに絞って対策を行うという選 択肢もあるだろう。

    「データベースを更新する機能のみCSRF対策をすればよい」のような考えは正しくない。どの機能について対策を行うかは、ウェブアプリケーションの運営 者や開発者が決めるべき(つまり、ウェブアプリケーションの仕様に含まれるべき)事である。

    ■ひとつの機能は2画面で構成される

    それがどんな機能であれ、ひとつの機能は基本的には2画面で構成される。CSRF対策を考える上で、このことをしっかり認識しておく必要がある。

    例としてまず「ログインする」という機能について考えてみる。ユーザはログインしたいサイトのトップページなどにアクセスする。するとブラウザにはユーザ IDとパスワードの入力欄を持つ画面が表示される。これが「画面1」である。次にユーザはユーザIDとパスワードを入力し、「ログイン」のボタンをクリッ クする。するとブラウザには「ようこそ金床さん。」のようにログインに成功したことを示す画面が表示される。これが「画面2」である。

    2つめの例として「日記を書く」という機能について考えてみる。ユーザは日記を書くためのページへアクセスする。するとブラウザには日記を記入するための テキスト入力欄を持つ画面が表示される。これが「画面1」である。ユーザはテキスト入力欄に日記を書き、「書き込む」というボタンをクリックする。すると ブラウザには「日記を書き込みました」などの画面が表示される。これが「画面2」である。

    3つめの例として検索エンジンを使った「検索する」という機能について考えてみる。ユーザはGoogleやYahoo!などのサイトのトップページにアク セスする。するとブラウザにはキーワード入力欄を持つ画面が表示される。これが「画面1」である。次にユーザは検索したいキーワードを入力し、検索ボタン をクリックする。するとブラウザには検索結果が表示される。これが「画面2」である。

    最後の例として「ログアウトする」という機能について考えてみる。ユーザは既にウェブアプリケーションにログイン済みである。そのため、ブラウザに表示さ れている画面の隅には「ログアウトする」というリンクやボタンがある。この画面が「画面1」である。ユーザが「ログアウトする」をクリックすると、「ご利 用ありがとうございました」などの画面が表示される。これが「画面2」である。

    HTTPレベルで考えると、まず画面1を表示する際に一度リクエストとレスポンスのやりとりが発生する。そして画面2を表示する際に、さらに一度リクエス トとレスポンスがやりとりされる。以下の説明ではこれらをそれぞれ

  • リクエスト1
  • レスポンス1
  • リクエスト2
  • レスポンス2

  • と呼ぶことにする。

    サーバー側に存在するプログラムは、リクエスト1を受信すると何かしらの処理を行い、レスポンス1を送信する。また同様に、リクエスト2とレスポンス2の 間にも処理を行う。これらの処理をそれぞれ

  • 処理1
  • 処理2

  • と呼ぶことにする。また、レスポンス1、レスポンス2によってブラウザに表示される画面をそれぞれ

  • 画面1
  • 画面2

  • と呼ぶことにする。

    ■「CSRF以前の問題」については考えない

    CSRF対策を考える上で、非常識的な「CSRF以前の問題」が存在すると考えていては話が先に進まない。「CSRF以前の問題」とは次のようなものだ。

  • アプリケーションにXSS脆弱性が存在する
  • 攻撃者が正規ユーザのトラフィックを盗聴可能な状態である
  • 正規ユーザのマシンにスパイウェアが入りこんでいる
  • 正規ユーザが悪意あるプロキシサーバーを経由して通信している
  • 正規ユーザが催眠術で攻撃者に操られている

  • このような仮定の下ではCSRF対策を議論する意味がない。以下の説明ではウェブアプリケーションを取り巻く状況はごく常識的なものであるとする。

    ■正しいCSRF対策

    それでは、筆者が安全だと考える具体的なCSRF対策について説明する。これらの方法のうち、どれかひとつだけを採用すればよい。本稿では現在最もポピュラーであると思われる、Cookieを利用して認証を伴うセッション管理を行うウェブアプリケーションを対象とする。認証を伴うウェブアプリケーションにおけるCSRFは、ウェブブラウザがHTTPリクエストに対して自動的に認証情報を付加してしまう場合に対策が必要となる。これにはCookieにセッションIDを格納する場合、Basic認証を用いる場合、SSLクライアント認証を用いる場合がある。

    認証を伴わない場合のCSRF対策はCookie Monster Bugの影響なども受けるため、やや複雑となる。また、被害がいたずらのレベルに止まるケースが多いと考えられるため、本稿では対象としない。


    ■正しい対策その1: ワンタイムトークンを正しく使用する方法

    まずひとつめはワンタイムトークンを使う方法である。ここで題に「正しく使用する方法」と付けたのには理由がある。ひとくちに「ワンタイムトークンを使 う」と言っても開発者によって実装方法がまちまちであり、場合によってはCSRF対策とならないワンタイムトークンの使い方をしてしまうことがあるのだ (後ほど実例を挙げる)。そこでここでは押さえるべきポイントを明確にした、ワンタイムトークンの正しい使用方法を紹介する。

    まず、アプリケーションの作りとして、リクエスト1はGETよりもPOSTを用いる方が好ましい。通常、POSTリクエストに対するレスポンスはキャッシュに残らないため、POSTを使用することで、hiddenフィールドに格納したトークンがキャッシュに残ってしまう可能性を下げることができる。また、処理1には「トークンの発行」という副作用があるためだ。

    リクエスト1を受信後、サーバー側のプログラムは処理1を開始する。

    トークンを生成する。トークンにはセッションIDなどと同様に、充分な長さを持つ予測不可能な文字列を使用する。

    トークンをセッションと結びつけた形でサーバー側で保存する。いわゆるセッションオブジェクトに格納するのが分かりやすい方法だ。

    hiddenフィールドにトークンを格納したレスポンス1をクライアントへ送信する。この際、レスポンス1がキャッシュに残らないようにするため、適切な ヘッダーフィールドを付ける。

    処理1は以上となる。

    続いてクライアントはトークンを含んだリクエスト2を送信してくる。このリクエストのメソッドはGETでもPOSTでもよい。

    リクエスト2を受信後、サーバー側プログラムは処理2を開始する。まず、送られてきたトークンとセッションが正しい組み合わせであることを、処理1の際に サーバー側に保存しておいた情報を用いて確認する。この組み合わせが正しくない場合にはエラーとして扱い、処理をそこで終了する。組み合わせが正しい場合 には次にすすむ。

    サーバー側で保存していたトークンの情報を破棄する。これによってトークンが「ワンタイム」であることになる。

    以上でワンタイムトークンの処理が終了する。続いて「日記を書き込む」などの、アプリケーション本来の機能を実行する。

    以上が正しい対策その1である。


    ■正しい対策その2: 固定トークンを使用する方法

    毎フォームごとに異なる値のトークンを用い、一回限り有効とするのが「正しい対策その1」に挙げたワンタイムトークン方式である。これに対し、全てのフォームについて同じ値を使用する方法がここで説明する「固定トークン」方式である。この方式もCSRF対策としては正しく機能する。安全性ではワンタイムトークン方式に劣るが、実装が比較的簡単になるという利点を持つ。

    ブラウザの「戻る」ボタンを押し、再度submitする場合の挙動などがワンタイムトークン方式とは異なるため、環境によって適切な方法を選択するのがよいだろう。

    セッション毎に一つの固定トークンを使う方法や、ユーザ毎に一つの固定トークンを使う方法が存在する。

    セッション毎に一つの固定トークンを用いる場合、単純にセッションIDのハッシュ値をトークンとして用いるのは好ましくない。セッションIDとトークンの値はどちらも攻撃者が欲しがる情報なので、どちらかが知られてしまった場合に、もう一方が簡単に計算できるような状況は避けるべきだ。

    また、セッションIDというのはあくまでもその時点でのセッションオブジェクトの識別子にすぎない。2006年現在ではセッションIDはセッションを通してあまり変更されないものだが、今後、より高いセキュリティを実現するために頻繁にセッションIDを変更するような実装のミドルウェアが登場することも考えられる。トークンはあくまでもセッションオブジェクトと結びつけておくべきであり、セッションIDそのものに結びつけるべきではない。従って、トークンは独立したものとして扱い、ユニークな文字列を生成して使用するのがよい。

    実装については「正しい対策その1」とほぼ同じであるが、リファラーからの漏洩を防ぐため、リクエスト2はPOSTでなければならない。


    ■正しい対策その3: パスワードの再入力を求める方法

    サーバー側で生成するトークンではなく、ユーザだけが知っているパスワードを用いてフォームの送信が正しいものであることを確認し、CSRFを防ぐ方法である。SSLとの併用が好ましいだろう。

    リクエスト1はGETでもPOSTでもよい。

    処理1は何も行わない。

    ユーザは画面1においてパスワードを入力する。そしてパスワードを含むリクエスト2が送信される。画面2の中のリンクなどからリファラーを通じてパスワー ドが漏洩してはまずいので、このリクエスト2はPOSTでなければならない。

    処理2において、サーバー側プログラムは送信されてきたパスワードが正しいかどうかをサーバー側のデータベースなどの情報を用いて照合する。パスワードが 間違っていればエラーとして扱い、処理を停止する。

    以上でパスワード再入力によるCSRF対策の処理が完了する。続いて「日記を書き込む」などの、アプリケーション本来の機能を実行する。

    以上が正しい対策その3である。

    ■正しい対策その4: CAPTCHAを正しく使用する方法

    CAPTCHAはトークンの一種である。トークンがhiddenフィールドに格納された文字列ではなく、画像データとしてクライアントへ送られるという点 が上で説明したワンタイムトークン方式との違いだ。CAPTCHAは本来、HTTPクライアントを操作しているのが人間なのか、あるいは自動化されたプロ グラムなのかを判別するために使われる技術であり、CSRF対策とは無関係だ。ただ、CAPTCHAはトークンの一種であるので、正しくトークン方式として使われれば、結果としてCSRF攻撃を防ぐことができる。

    CAPTCHAを使用する方法はつまりはワンタイムトークン方式である。そのため、上で説明したhiddenフィールドを使ったワンタイムトークン方式に 比べ、CSRF対策としての利点はない。また、ユーザビリティや実装の複雑さで劣るため、進んでCAPTCHA方式を採用する理由は存在しない。それがワ ンタイムトークンであることを知らずに「CSRF対策にはCAPTCHAを使おう。画像だから安全だ。」と考えるのは明らかに誤りなのである。

    それでもCAPTCHA方式を採用する場合には、それが正しくワンタイムトークンとして動作するよう、次のように実装する必要がある。

    レスポンス1にはトークンのような秘密情報が含まれない。そのため、リクエスト1はGETでもPOSTでもよい。

    レスポンス1にはCAPTCHA画像へのリンクが含まれる。そのため、自動的にCAPTCHA画像へのHTTPリクエストが発生する。
    CAPTCHA画像を含むレスポンス(レスポンス1.5とする)はキャッシュされないよう適切なヘッダーフィールドを持つ必要がある。

    CAPTCHA画像に含まれるトークンはセッションと結びつけた形でサーバー側で保存しておく。

    ワンタイムトークン方式と同様、リクエスト2はGETでもPOSTでもよい。

    処理2はワンタイムトークン方式と同じように行う。

    以上が正しい対策その4である。これはCSRF対策として正しく機能するが、先述の理由により推奨しない。

    ■Basic認証、SSLクライアント認証の場合

    Basic認証やSSLクライアント認証を用いて「ログイン」機能を提供しているアプリケーションでも、必要なCSRF対策は基本的には同じである。サーバー側ではトークンをユーザ個別の情報と結びつけて保存しておき、フォームが実行された際に照会する。

    ■リファラーについて

    Flashを悪用することでリファラーを偽造できるという報告がある。そのため、リファラーはCSRF対策では使用するべきでない。

    また、リファラーを調べることにより、攻撃者がCSRF攻撃のために仕掛けた罠ページを発見できる可能性がある。そのため、実際にCSRF攻撃が行われて いるのかどうかを把握したい場合には、リクエスト2のリファラーを(外部サイトからのものに限り)記録しておくとよい。

    ■SSLについて

    前述したようにウェブアプリケーションの認証にSSLクライアント証明書を使用しているアプリケーションでも、トークン方式などを用いたCSRF対策が必要である。

    暗号化などを目的としたSSLの使用も、CSRF対策にはならないので、別途トークン方式などを用いたCSRF対策が必要である。SSLの通信はキャッシュされない、また盗聴されにくいなどの利点もあるため、可能な場合には利用するのがよい。

    ■採用すべきでないCSRF対策

    ここからは筆者がウェブサイトや書籍で見つけた、採用すべきでないCSRF対策について説明する。これらの対策は意味がなかったり、かえってシステムを危 険な状態にしてしまうので、実施してはいけない。

    ■採用すべきでない対策その1: セッションIDをトークンとして使う

    2006年3月の時点で、国内の多くのウェブサイトや書籍などで紹介されている方法である。

    この方式は固定トークン方式の一種でありCSRF対策としては正しく機能するが、トークンの漏洩が即セッションIDの漏れに繋がってしまうため、万が一の場合にCSRFによる攻撃だけでなく、セッションハイジャックも許してしまう可能性が出てくる。繰り返しになるがトークンの値とセッションIDの値はどちらも攻撃者が欲しがる情報であるため、万が一の場合にどちらかが漏れてしまっても、もう一方には影響しない形であることが望ましい。

    ■採用すべきでない対策その2: 誤ったトークン方式

    トークン方式では、トークンは必ずセッションと結びつけて扱う必要がある。そうでない場合、攻撃者自らが画面1にアクセスして取得したトークン を罠のページに埋め込む「トークン固定攻撃」が可能となってしまうためだ。そのため、このような実装をしてしまうとCSRF対策にならない。誤ったワンタ イムトークン方式とは、このような「セッションとトークンの結びつき」を持たない方式を指す。CAPTCHA方式でも同様に、セッションとの結びつきが必要となる。

    ■採用すべきでない対策その3: リクエスト2をPOSTにする

    IMGタグなどを利用したCSRF攻撃の例を見ると、「ではリクエスト2がPOSTで送られるようにすればよい」と考えてしまいがちだが、 JavaScriptを利用することで攻撃者は罠のページからPOSTリクエストを送らせることができるので、この方法は対策にならない。具体的には次の ようなコードが使われる。
    <form action="page2" method="POST">
    ...(略)
    </form>

    <script>
    document.forms[ 0 ].submit();
    </script>

    ■採用すべきでない対策その4: 確認画面を挟む

    確認画面を挟む方法には2通りの実装が考えられる。ひとつは確認画面がhidden属性で全ての(確認画面に対してsubmitされた)フォームの値を持 ち、3画面目(実行画面、あるいは決定画面)にジャンプする際に再度それをsubmitするというもの。もうひとつは確認画面を表示する時点でサーバー側 のセッションオブジェクトなどにフォームの値を保存しておき、3画面目を処理する際にそれを取り出してデータベースなどを更新するものである。

    ひとつめの方法は罠のページから直接3画面目にCSRF攻撃を行うことで回避されてしまうので、対策にはならない。

    2つめの方法は罠のページに複数のiframeを設置し、それぞれに読み込まれるページから確認画面と3画面目へのCSRF攻撃を(必要があれば時間差 をおいて)それぞれ行うことで回避されてしまうので、これも対策にならない。

    それでは、元々の作りとして確認画面を含んだ3画面によって構成されるアプリケーションでは、どのようにCSRF対策を行えばよいのだろうか。これは先述 の通り、ひとつの機能は2画面で構成されるという原則に沿って考えるとよい。1画面目と2画面目(確認画面)によって「ユーザが入力した内容を表示する」 というひとつの機能が実現されている。さらに2画面目と3画面目で「ユーザが入力した内容をデータベースに登録する」のような別の機能が実現されている。 つまり、ここではひとつではなく、2つの機能が連続して実行されているのだ。

    従って、CSRF対策の基本であるワンタイムトークン方式を、それぞれの機能について適用すればよい。

    まずひとつめの機能に対するCSRF対策として、1画面目でワンタイムトークンを発行し、2画面目を表示する際の処理でこのトークンが正しいかどうか確認 する。これによって、ひとつめの機能に対するCSRF対策が実現する。そしてこの2画面目を生成する処理の際にさらに異なるワンタイムトークンを発行し、 3画面目でこの2つめのトークンが正しく送られてくることを確認する。これによって2つめの機能に対するCSRF対策が実現し、結果としてこの3画面に よって実現される(ひとつに見えるが実は2つからなる)機能全体のCSRF対策が実現されることになる。

    ■採用すべきでない対策その5: セッションIDを細かく変更する

    処理1でセッションIDを切り替え、このセッションIDを持たない場合には処理2を行わないようにする、という方式である。「確認画面を挟む」方式と同様 に、罠のページに複数のiframeを設置し、画面1と画面2に連続してCSRF攻撃を行うことで回避されてしまうので、この方式は対策にならない。

    ■採用すべきでない対策その6: URLを利用したセッション管理方式を採用する

    Cookieではなく、URLを用いたセッション管理方式では、CSRF攻撃を防ぐことができる。しかしリファラーやアクセスログなどからセッションIDが漏れる可能性が高いため、セッションハイジャックなどに繋がる恐れがある。そのため、筆者はこの方法は推奨しない。

    ■採用すべきでない対策その7: 画面遷移を制御する

    ユーザがウェブアプリケーションの意図した画面遷移を行っているかどうかを確認することでCSRFを防ぐことができるとする説明が存在する。しかしこれは誤りである。攻撃者は罠のページに複数のiframeを設置することで画面遷移をコントロールすることができる。そのため、画面遷移の制御はCSRF対策にならない。


    ■CSRF用メーリングリスト

    筆者が本稿を書いた目的は、本当に正しいCSRF対策とは何なのかという疑問の答えを得ることである。また、CSRFにまつわる情報の混乱を解決したいと いう思いもある。現時点では本稿に記した対策方法が正しいものであると考えているが、誤っている可能性ももちろんある。内容に誤りがあれば、その都度修正 していきたい。修正する場合、本稿は新しい文章として書き換えてしまい、古いものは古いバージョンとして別名で保存し残していく形をとるつもりだ。

    本稿の内容についての意見や指摘などは大歓迎である。CSRFを中心にウェブアプリケーションセキュリティについて話し合うための場所として、メーリング リストを作成した。本稿に対するレスポンスがある場合にはこのメーリングリストを使用して頂ければありがたい。CSRFに関しては広くアンテナを張ってい るつもりなので、ブログなどで言及してもらった場合にも反応するつもりだ。

    Sea Surfers ML
    http://www.freeml.com/ctrl/html/MLInfoForm/seasurfers@freeml.com

    Sea Surfers ML に入ろう! [利用規約]
    FreeML メールアドレス

    また、本稿で取り上げていない、ウェブサイトや書籍の中の細かい気になる点についても、このメーリングリストの中で触れたいと考えている。

    ■関連リンク

  • おさかなラボ http://kaede.to/~canada/doc/
  • PHPサイバーテロの技法(書籍) http://www.amazon.co.jp/exec/obidos/ASIN/4883374718/
  • PEAK XOOPS Support&Experiment http://www.peak.ne.jp/xoops/md/news/
  • 俺専用mxxi :: ぼくはまちちゃん! http://mxxi.hamachiya.com/
  • hoshikuzu | star_dust の書斎 http://d.hatena.ne.jp/hoshikuzu/
  • 児童小銃 .456 http://d.hatena.ne.jp/rna/
  • INNOCENT CODE(書籍) http://www.amazon.co.jp/exec/obidos/ASIN/0470857447/


  • ■古いバージョン

  • Version 1.2 http://www.jumperz.net/texts/csrf1.2.htm
  • Version 2.0 http://www.jumperz.net/texts/csrf2.0.htm
  • Version 2.1 http://www.jumperz.net/texts/csrf2.1.htm
  • Version 2.2 http://www.jumperz.net/texts/csrf2.2.htm
  • Version 2.3 http://www.jumperz.net/texts/csrf2.3.htm


  • Scutum SaaS/ASP型WAFサービス 【スキュータム】 ←筆者が開発しているSaaS型WAFサービスはこちら

    2009年7月11日 Version 2.4