開発者のための正しい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、レスポンス2によってブラウザに表示される画面をそれぞれ と呼ぶことにする。 ■「CSRF以前の問題」については考えないCSRF対策を考える上で、非常識的な「CSRF以前の問題」が存在すると考えていては話が先に進まない。「CSRF以前の問題」とは次のようなものだ。このような仮定の下では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月の時点で、国内の多くのウェブサイトや書籍などで紹介されている方法である。 |