バカンス駆動開発

この前バカンスって言ったら「古っ」って言われました

AjaxにおけるCSRF攻撃対策

前回で近いうちにAjaxのセキュリティについて書きます!と宣言しましたが、あれはつまりCSRF攻撃の対策についてでした。今回はAjaxCSRF対策を行う方法を書きます。

先に結論

Ajax通信におけるCSRF攻撃対策は通常の遷移時に施す対策と考え方はかわりません。

実装方法

  1. ログイン時にトークンを生成しセッションオブジェクトにセットし、トークンをクライアントに送信
  2. クライアント側のjsスクリプト内でトークンをAjax通信時に常に付加するように設定
  3. サーバー側でAjaxで送信されたトークンの有無とセッションオブジェクト内のトークの一致を確認
  4. ログアウト時にセッションは全て破棄

ここで生成するトークンはワンタイムではないです。ログイン時にずっと使いまわします。

ところで、パーフェクトPHPチルドレンとしては見逃せないエントリが

CSRF対策のトークンをワンタイムにしたら意図に反して脆弱になった実装例 - 徳丸浩の日記(2011-01-27)

なんと体系的に学ぶ 安全なWebアプリケーションの作り方の著者がパーフェクトPHPのセキュリティにツッコミ入れているという。

徳丸氏の主張は

  • トークン生成に時刻情報(microtime())を用いるべきではない。
    • 時刻情報は推測可能だから
    • またパーフェクトPHPの場合その都度トークンを発行しているからなおのこと時刻を予測しやすくなっている(攻撃者の好きな時刻でトークンを発行できるため)
      • 例えば、ログイン時の時刻を元に生成していれば予測困難度は上がった。(ユーザーのログイン時刻を取得するのは攻撃者にとって困難)
  • CSRF攻撃対策にトークンを使う場合、重要なのは予測困難度であって、ワンタイム性ではない(著書でも同様のことを書かれています)

また、この記事のコメント欄でこんな記事を見つけました。

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

これらの記事を読むとトークンはたしかにワンタイムである必要はないと思います。

Twitterのソースを見てみる

Twitterのソース見る限りツイート一つ一つや画面のあちこちにあるフォームにそれぞれ異なるトークンがあるわけじゃなく、画面に1種類のトークンがありました。具体的にどのように使っているかはさっぱりわかりませんでしたが、以下の事はわかりました。

  • 画面遷移してもトークンの値変わらず(例:トップページから設定画面)
  • ログアウト→ログインでトークン再生成
  • ツイート、お気に入り、ツイートの削除等のアクションでもトークンは維持

このことから、おそらくTwitterはセッション単位でトークンを使いまわしているようです。僕も天下のTwitterに従います。

具体的な実装

ログイン時にサーバー側でトークンを生成、セッションにセットまでは省略します。

テンプレートか何かの中で以下を出力。

<?php
//テンプレート
echo "<script>var token = {$token}</script>";
?>

これで毎回クライアントのjsにはvar tokenにはトークンがセットされます。

次に、このトークンがAjaxリクエスト時に毎回自動的にサーバーに投げられるようにします。

$( document ).ajaxSend(function(event, jqxhr, settings) {
    jqxhr.setRequestHeader('X-CSRF-Token', csrf_token);
});

ajaxSend()jQueryメソッドの一つで、Ajaxリクエストが発生時、リクエスト開始前に実行される関数をバインドするメソッドです。(.ajaxSend() | jQuery API Documentation) 第2引数にXHRオブジェクトを持ちます。setRequestHeader()メソッドはHTTP リクエストヘッダの値を設定します。これで、ajaxリクエストの際はヘッダにトークン情報が常に入っています。あとはサーバー側でチェックするだけです。

<?php
if ( isset($_SERVER['HTTP_X_CSRF_TOKEN'])
    && strtolower($_SERVER['HTTP_X_CSRF_TOKEN']) == 'abcdefg1234') { //トークン
    
    return true;
}

以上で、Ajax通信におけるCSRF攻撃対策は完了です。

おまけ1

ワンタイムトークンを使う事が脆弱性があるということではない、と徳丸氏も記事のコメント欄で言及されています。ただ、それでもajaxを多用する画面でワンタイムではなく固定トークンを僕が選んだ理由はワンタイムにするとトークンの管理がややこしいと思ったからです。

例えばTwitterのような画面に何個もつぶやきがある画面で、それぞれのつぶやきに対して削除のようなトークンを消費するアクションがあるサービスの場合、一体何個のトークンを用意したらええねんと悩みましたが良い答えが見つからず。また機会があれば記事を書きたいです。