2013年10月15日火曜日

session_regenerate_id関数の第1引数はtrueにすべきか

以前tumblrに書いたエントリ「データベースのデータを信用してはいけないか?」にて、PHP技術者認定試験の想定問題について取り上げましたが、その後、書籍「徹底攻略 PHP5 技術者認定 [上級] 試験問題集 [PJ0-200]対応」が刊行されたことを知り、購入しました。
同試験は、比較セキュリティの配点が高い(12%)ことから、試験問題集にはセキュリティの独立した章として第10章が割り当てられ、セキュリティの問題が21個集められています。

先のエントリで紹介した「ITトレメ PHP技術者認定・初級 過去問題一覧 - @IT自分戦略研究所」の問題を見た時の印象は、問題の癖が強く、独自の用語を使っている箇所が多いことが懸念点でしたので、そのような観点から同書第10章「セキュリティ」の問題を確認したところ、全体的に下記の印象を持ちました。
  • 用語としてIPA等で使われている一般的なもの(例:静的プレースホルダ)が用いられ、妥当と考えられる
  • 対策手法についても、IPA「安全なウェブサイトの作り方」などで説明されている方法を正とし、独自の手法はほとんど見られない
  • 「上級試験」ということで、かなり際どいところを攻めているという印象
ということで、私が懸念した問題は見当たりませんでした。執筆陣の努力を称賛したいと考えます。
しかし、最後に指摘した「際どいところ」に関しては、正誤という点では問題ない(力のある解答者なら正当に到達できる)ものの、微妙なところで議論の余地があるなと感じました。(こちらの「大、小、展、外、誤」参照)。
それは、国語入試問題必勝法的な微妙さであって、明らかに正しい選択肢を載せるとすぐに答えがわかるために、微妙な問題を残すものを正答としているのではないかと勘ぐりたくなりました。
ここでは、その例として、セッション固定化攻撃の対策方法として、session_regenerate_id()関数の使い方に関する問題を取り上げます。

session_regenerate_idの第1引数はどうすべきか

同書P324のセキュリティ問題13には、セッション固定化攻撃の対策として適切なものを2つ選択するように支持していますが、ここではその選択肢CとDに関係する話題です。元の本が問題集なので、引用はせずに要旨のみを示します。
if (認証OKの場合) {
  $_SESSION['auth'] = true;
  session_regenerate_id( ● );
}
●のところ、片方が空、片方は true となっています。実は「国語入試問題必勝法」的には正答はあきらかで、trueを指定した方が正答です。この関数の第1引数は、元のセッションIDに紐づくセッションを破棄することを指定するもので、過去のセッションを破棄したほうが安全方向に倒れることは明らかです。すなわち、これがセキュリティの問題という前提では、trueを指定しないほうが正答ということは通常ありえないわけで、先に「正誤という点では問題ない」と書いた理由はこれです。
しかし、上記のスクリプトを検討すると、微妙な問題、いや、はっきり言えば正答にもバグがあります。一方で、誤答の方にも実質的な危険性があるわけではありません。

誤答がただちに危険なわけではない

問題文のスクリプトは、まずセッションに「認証状態にある」ことをセットしたあとで、セッションIDの再生成を行っています。このため、問題の趣旨としては、元のセッションIDに対して認証状態がいったんセットされるため、古いセッションを破棄しないとセッション固定化攻撃が成立してしまうとしています。
しかし、このスクリプトを動かしてみると分かりますが、古いセッションを保存するファイルには、認証情報は保存されません。その理由は、session_write_closeが呼ばれていないからです。ファイルに認証情報が保存されていない以上、そのファイルを積極的に破棄する必要もありません。例外として、「ログイン前セッション固定化攻撃」の問題がありますが、これについて別のエントリにしたいと思います。
ということで、誤答(trueを指定しない方)に実質的な危険性があるわけではありません。

正答が模範的なわけではない

一方、正答に問題がないわけではありません。現状のスクリプトでも外部から攻撃できるわけではありませんが、PHPのセッションが仮にsession_write_closeを呼ばなくてもファイルに書き込まれる実装に変更された場合は、一瞬とはいえ元のセッションIDで認証状態になります。これは直ちに、session_regenerate_id関数の第1引数 true により削除されるわけですが、いわゆるレースコンディションの状態となり、第三者にセッションハイジャックされる危険性があります。これらを下表にまとめました。

第1引数falseあるいは指定なしtrue
現状のPHPの実装問題なし問題なし
セッションが直ちにファイルに書き込まれる実装セッションハイジャックされるレースコンディションによりセッションハイジャックされる可能性

本来は、認証確認後に直ちにsession_regenerate_idすべし

前述のように、この問題は現状のPHPの仕様(実装)では誤答・正答とも実質的な問題はありませんが、PHPの仕様が変わると、正答の方がベターな書き方であるものの、ベストではない、ということになります。
では、ベストの書き方はどうかというと、下記のように、session_regenerate_idしてから認証状態をセッションにセットすべきです。
if (認証OKの場合) {
  session_regenerate_id( ● );
  $_SESSION['auth'] = true;
}
これにより、一瞬たりとも元のセッションIDが認証状態になることはないので安全です。そして、●の部分は、それ以前のセッションをどうすべきかで判断することになります。これはすなわち、「ログイン前セッション固定化攻撃」の対策をどうすべきかということになりますが、こちらは一層ややこしい問題であるので、別稿にて説明します。一般的には true を指定した方が安全ということには、異論ありません。

まとめ

PHP5 技術者認定 [上級] 試験問題集を題材として、session_regenerate_id関数の第1引数について検討しました。これをtrueにすることは、保険的な対策としては有効であると私も考えますが、trueを指定しないために脆弱性となる例を作るのはかなり難しいと考えます。
PHP5 技術者認定 [上級] 試験問題集の第10章13番の問題は、「trueを指定しないために脆弱となる」例を無理に作ろうとしたために、前述したように「際どい」問題になってしまいました。それにより、正答が模範的なスクリプトでないため、この問題集により「正しいPHPアプリの書き方が身につくわけではない」という点も気になったところです。(たぶん続く)

2 件のコメント:

  1. 「session_regenerate_idの第1引数はどうすべきか」の擬似コードの後の文章の「正答はあきらで、」は「正当はあきらかで、」でしょうか?

    返信削除
  2. ご指摘ありがとうございます。修正しました。

    返信削除

フォロワー