CSRF対策に固定トークンを推奨する

CSRF対策にワンタイムトークンが有効だという記事をよく見かけるけど、

固定トークンの方がユーザに遷移を強制しない分良い実装だと思うんだけどなんで浸透しないんだろう?


まず、CSRF可能なWebアプリは以下の遷移の様に

認証 → 入力 → 確認 → 完了

まず認証を通して、正規のユーザかどうかを確認した後に機能を提供する
Webアプリが攻撃対象になる。

ここでワンタイムトークンの場合
確認画面まで正規のユーザが遷移した時にトークンを発行し、
hiddenに値を入れておき、発行したトークンをセッション情報としてサーバに保存する。
正規のユーザが完了画面まで遷移した時にはhiddenの値とセッション情報にあるトーク
を比較し、イコールであれば正規のユーザからのリクエストだという事で処理を続ける。

PHPの場合、以下の様な実装になる。

・確認画面

[php]


">

[/php]

・完了画面
[php]

[/php]
一応これでCSRFの対策は完了だが、複数ウィンドウ開いて操作をした時に
トークンの値が変わる事で正規ユーザの完了画面への遷移が正規のアクセスではないと見なされる事がある。
5年も前、タブブラウザがまだ一般的で無く複数のサイトを見る際にはIEを10個くらい開いていた時代なら
この実装でも許されただろうけど(そもそもユーザがこういう使い方をしている限りCSRFは出来ないか)
今のご時勢、同時編集が出来ないWebアプリはユーザビリティを大きく損なうので、遷移を強制しないため
の対応が別箇必要になってしまう。


そこでオススメするのが固定トークン方式。

認証の画面で直ぐに(正確には認証通過後)トークンを発行しセッション情報に入れておき、確認画面で使用するトークンは
セッション中で固定されたトークンを使用する。完了画面でも、POSTされたトークンの値が
セッション中のトークンの値と一致するかどうかなので、ユーザがどう遷移してきても問題なく
処理を完了できる。


PHPの場合、以下の様な実装になる。

・認証画面
[php]
[/php]

・確認画面
[php]


">

[/php]

・完了画面
[php]
[/php]
ワンタイムトークンとの違いは、確認画面でトークンを発行するか認証画面でトークンを発行するかの違いのみ。
一度発行したトークンを使いまわすので、セキュリティ上問題がありそうだが、トークンが漏れる脆弱性がある場合
ユーザ認証に使用しているセッションも同様に漏れるはず、つまりセッションハイジャック可能。
セッションハイジャック可能なら攻撃者はCSRFを行うまでも無いので固定トークンとワンタイムトーク
どちらを採用してもWebアプリの堅牢性に差異は無い。


2007年11月25日追記



はてブで指摘がありましたが、確かにrand()じゃなくてmt_rand()を使うべきですね、ソースの該当箇所を
修正しました。uunfoさんご指摘ありがとうございます。