脆弱性やセキュリティ対策について技術的な話ばかりしていたので「それで結局PHPのセッション管理どうすれば良いの?」と思われた方も多いと思います。簡単にまとめます。漏れや追記した方が良い事などがあったらご指摘ください。
session_regenerate_id()の使い方(セッションID再生成)
- ログイン処理時・ログアウト処理には最初にsession_regenerate_idを呼ぶ。
- 定期的にsession_regenerate_idを呼ぶ。間隔が短いほど低リスク。
session_regenerate_idはtrueオプション(古いセッションデータ削除)を付ける。- session_regenerate_idでtrueオプションは付けない。その代わりに再生成した時点のタイムスタンプを古いセッションデータに記録し、数分後からそのセッションデータへのアクセスを拒否する。方法はsession_regenerate_idのマニュアルページに記載しました。
session.save_pathの設定(セッションデータDBの設定)
- アプリケーションを分割する場合、session.save_pathを変更してセッションDBも分割する。
session.cookie_lifetimeの設定(セッションIDクッキーの寿命)
- デフォルトは0(0=セッションクッキー。ブラウザの終了と同時に削除されるクッキーの事。セッションクッキーとセッションIDクッキーは紛らわしいので、用語は明確に区別して使用する方が良い)特別な理由が無い限りデフォルトを使用する。
- 特別な理由がある場合も、長寿命なクッキーはリスク増加でしか無い事に留意する。自動ログインに長い寿命のセッションIDを利用してはならない。参考:解答:まちがった自動ログイン処理
session.gc_maxlifetimeの設定(セッションデータの有効期限)
- できる限り短く設定する。ただし、ユーザの利便性とのバランスを考慮すること。
- 長く設定する場合、session_regenerate_idで適切な間隔(長いほど高リスク)でセッションIDを再生成する。
- GCでは正確な有効期限が設定できない。セッションデータの有効期限管理は$_SESSION配列にタイムスタンプを設定して管理する。
- タイムスタンプを利用し有効期限を管理し、有効期限切れのセッションはリセットする。e.g. if ($_SESSION[‘atime’] < time() – ini_get(‘session.maxlifetime’)) $_SESSION = array();
- タイムスタンプを毎回更新してはなりません。現在のPHPは$_SESSION配列の更新が無かった場合、書き込みを行わないことで性能を向上させています。数分ごとに更新する if ($_SESSION[‘atime’] < time() – 300) $_SESSION[‘atime’] = time(); といったコードで更新頻度を制御する。
- 詳しくはsession_regenerate_idのマニュアルページを参照。
- PHP 7.1からはsession_gc関数が利用できる。cronなどで定期実行しGCを実行する。
session.gc_divisorの設定(GC発生確率)
- PHPソースのデフォルト100、php.ini-production/developmentは1000。アクセス数の少ないサイトではGCが発生しない事を意味する。アクセス数に応じた確率にする。
- 本来はアクセスタイムスタンプを$_SESSION配列で管理し、有効期限切れを制御する。
- PHP 7.1からはsession_gc関数が利用できる。cronなどで定期実行しGCを実行する。
session.nameの設定(セッション名の設定)
- アプリケーションに固有の名前をつける。
- ランダム文字列のセッション名にする。(より低リスク)
session.cookie_secureの設定 (HTTPSのみの設定)
- デフォルト無効。SSLしか使わないなら必ず有効にする。その場合、HSTSも合わせて有効にする。
- 備考:HTTPからHTTPSへの移行にはUpgrade-Insecure-Requestsも利用できる。
session.cookie_httponlyの設定(HTTPのみクッキーを送信=Javascriptにアクセスさせない)
- デフォルト無効。有効にする。JavascriptからのセッションIDアクセスはリスク増加でしかない。
- 稀にCSRF対策によっては無効にできない場合もある。その場合、できるだけCSRF対策を変える。
session.cookie_domainとsession.cookie_pathの設定(セッションIDクッキーのドメインとパス)
- ブラウザによって優先順位が異なるので、デフォルトのままでよい。
- 同じセッション名となり、困る場合は適宜ドメインかパスを設定する。またはセッション名を変える。
session.hash_functionの設定 (セッションID生成関数)
- 1 (SHA1)に設定する。0(MD5)よりSHA1の方が低リスク。
- PHP 7.1以降はsession.sid_lengthが利用できる。32文字以上を利用する。
session.strict_modeの設定(アダプション対策)
- デフォルトは無効。
- session.strict_mode=1(有効)に設定する。
以下はstrict_modeの説明です。
- 在弱なアプリでも一定の保護が可能で攻撃を検出できる。
- セッションDBを分割していれば、分割されているフィクセイションに脆弱なアプリがJavascirptインジェクションに脆弱なアプリからの影響を受けない。(特定ユーザに対するDoSは可能だが、これはsession_regenerate_id()を呼んでいる場合も同じ)
- session_regenerate_id()を適切に呼ぶ事は必須。
- 同じセッションIDが生成されてしまうコリージョンに対応。
- 3rdパーティ製のセーブハンドラモジュールはsession.strict_modeに対応していない場合がある。
- ユーザセーブハンドラも自らのコードの中で対応しなければならない。
3rdパーティ製のセーブハンドラモジュール、ユーザセーブハンドラがそれぞれ対応しなければならないのは、セッションデータへのアクセス方法がハンドラによって異なるためです。当初のパッチではセッションIDを確認する為のAPIをモジュール、ユーザハンドラに作成していましたが、セーブハンドラモジュールのABI維持の為に削除する事になりました。PHPスクリプトでセーブハンドラを作っている方は、READ時にセッションIDの確認を行うようにコーディングしてください。
補足
session_httponlyの有効と無効が反対になっていたので修正しました。須藤さん、鈴木さん、ありがとうございました。
2016/09/29 PHPマニュアルのSession Securityのページを大幅に加筆しました。こちらも参考にしてください。