« セッションアダプションとセッションフィクセイションとセッションハイジャックの違いとは | PHPのセッションアダプション脆弱性は修正して当然の脆弱性 » |
セッションアダプション脆弱性がないセッション管理が必要な理由
徳丸さんから「ブログ読みました。サンプルも動かしました。問題は分かるのですが、セッションアダプションがないPHPだと、何が改善されるのかが分かりません。教えて下さい 」とあったのでツイッターで返信するには少し長いのでこちらに書きます。まだ直していない脆弱性を詳しく解説するのはあまりよくないのですが、今なら影響を受けるアプリはほぼないと思うので構わないでしょう。
まず前提として、Webサーバと同様にJavascriptからもクッキーが設定できます。Javascriptインジェクションに脆弱なコードがサイトに1つでもあると、サイト上のセッションアダプション脆弱性をもつアプリへの攻撃が可能になります。この攻撃を緩和する対策もありますが、それはそれ、これはこれなので省略します。
セッションアダプションに脆弱なセッション管理機構で困る代表的なケースは以下の通りでしょう。
- ログイン時にsession_regenerate_idを呼んでない
- 自動ログインなど通常のセッション用以外でsession_regenerate_idを呼んでいない
- session_regenerate_idを呼んでいても、途中で保存している
- session_regenerate_idを呼んでいても、途中で実行パスが変えられる
...
1.は当たり前なので説明は省略しますが、アダプション脆弱性を利用できる状況だとログインセッションを乗っ取られます。
2.は、お薦めする方法ではありませんが、複数のセッションを用いて自動ログインを実装する方法です。php.ini設定でセッションデータの寿命は保存場所を変えられるので、自動ログイン用に寿命の長いセッションを別つくる事ができます。通常セッション用のみでなく、こちらもセッションIDを再生成しないとログインセッションを乗っ取られます。
3.は、2を実装する場合などに作ってしまいそうな脆弱性です。現在のログインセッションを作った後に、session_regenerate_idを呼ぶ前にsession_write_closeを使って書きこむと、既知のセッションIDを持つログインセッションが出来ます。
4.は、ログイン処理中に実行パスが変えられる場合に問題になります。ログイン処理中に前回のログイン状態を再生するなどの操作を行う場合、オブジェクトの再生成などでエラーや例外が発生し、プログラマが意図しない実行パスが変えられてしまい、session_regenerate_idが呼ばれない場合があります。これは多少気づきづらいとは思いますが、同類の攻撃が可能な脆弱性がPHP内部に存在して問題になった事があります。PHPに限らず例外などで実行パスが変えられてしまう事により問題となる事が時々あります。
session_regenerate_idのコードは単純なのでCプログラマ以外の方でも見れば何をしているのか解ると思います。
static PHP_FUNCTION(session_regenerate_id)
{
zend_bool del_ses = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &del_ses) == FAILURE) {
return;
}
if (SG(headers_sent) && PS(use_cookies)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot regenerate session id - headers already sent");
RETURN_FALSE;
}
if (PS(session_status) == php_session_active) {
if (PS(id)) {
if (del_ses && PS(mod)->s_destroy(&PS(mod_data), PS(id) TSRMLS_CC) == FAILURE) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Session object destruction failed");
RETURN_FALSE;
}
efree(PS(id));
PS(id) = NULL;
}
PS(id) = PS(mod)->s_create_sid(&PS(mod_data), NULL TSRMLS_CC);
PS(send_cookie) = 1;
php_session_reset_id(TSRMLS_C);
RETURN_TRUE;
}
RETURN_FALSE;
}
del_sesはPHP5.1.0から追加されたパラメータで、古いセッションデータを削除するオプションです。3のケースでもdel_sesがオプションでなければ、デフォルトで古いデータは消されるので問題にならないのですが、オプションなので消されない可能性が高くなります。
この実装を見て分かるようにsession_regenerate_idは基本的にはsession_id(md5('random string'))と変わりません。セッションアダプションに脆弱な場合でも、IDを変えて別のセッションデータにログイン状態を保存すれば、アダプションによってセッションIDが固定化されていても影響を受けずに済みます。PHPのWikiなどでは、実行パスの変更など条件は詳しく書いていませんが慣れているのか、どうして?という質問はもらいませんでした。
そもそも、セッションIDの再生成は盗聴などによるセッションID漏洩によるセッションハイジャックのリスクを緩和する為の対策です。それがセッションアダプションアダプション脆弱性の回避策として使えるので、問題がない使い方をしていればアダプション脆弱性を残しておいても良いことにはなりません。HTTPSを使っているからセッションIDをログイン・ログオフのイベントや定期的に変更しなくても良い、とは普通言いません。盗聴のリスクは少なくなりますが、万が一のセッションID漏洩の影響を緩和する為に適当に更新してください、と言うはずです。
先回りして議論しておきます。session_regenerate_idを呼ぶのは当然なので、アダプション脆弱性は脅威ではない、と主張することはセキュリティ専門家としておかしいです。この論法はセキュリティ対策の原則である多重のセキュリティ、フェイルセーフを否定しています。もし、この論法が正しいなら「文字エンコーディングのバリデーションはアプリの入力バリデーションを行う事が当然」としている私の場合、データベースやXML処理系などに文字エンコーディングベースの脆弱性があっても脅威ではなく、取るに足らない問題と言ってもよい事になります。
PHPの一利用者として、セッションアダプション脆弱性は対策さえしていればたいした問題でない、と言うことは構わないし当然の認識だと思います。しかし、セキュリティ専門家として、セッションアダプション脆弱性は対策さえしていればたいした問題でない、修正も必要ない、とするような論調で書くのはおかしいと言わざるを得ません。私はPHPの利用者でもありますが、開発者でもあります。ですから簡単に修正できる脆弱性、しかもWebセキュリティの核心と言えるセッション管理の脆弱性を放置したまま「たいした問題ではない」(脅威ではない)と言うととても無責任だと思うので言えません。
PHPの利用者が、たまたまスキルの足りないPHPプログラマに当たりアダプション脆弱性でセッション盗み放題のサイト作られてしまい問題になったとします。PHP開発者がセッション管理には他のプラットフォームでは当たり前に修正されている問題はあると認識した上で、直しもしないで「脅威ではない」「間違った使い方をした人が悪い」と言っていたらやり切れない思いを持つのではないでしょうか?
脱線しましたが、何が改善されるのか?結論は
- ログイン時にsession_regenerate_idを呼んでない
- 自動ログインなど通常のセッション用以外でsession_regenerate_idを呼んでいない
- session_regenerate_idを呼んでいても、途中で保存している
- session_regenerate_idを呼んでいても、途中で実行パスが変えられる
という事を考えなくてもアダプションによるログインセッション乗っ取りが行えなくなる事が改善点です。
関連:http://blog.ohgaki.net/php-session-adoption-how-it-works-to-attack-application
2 コメント
その場合、セッションが最初に初期化フラグをクッキーとセッション変数に保存します。フラグは適当なランダム文字列でよいです。おかしなセッションID用のクッキーが設定された場合、正しくsession_regenerate_idが呼ばれていると、セッション変数には初期化フラグが残らないか一致しない値が残ります。クッキーには初期化フラグがあるのにセッション変数には無い状態またはフラグの値が異なる状態になった場合、おかしなクッキーが設定されている状態です。
セッションデータがタイムアウトで削除されると、不正なセッションID用のクッキーとして検出されてしまいます。実際に実装する場合、フラグを保存するクッキーにフラグ、不正と検出された回数、秘密のの文字列のハッシュダイジェスト値を保存して不正と検出された回数が2回以上であるかチェックする必要があります。
セッションアダプション脆弱性が無くなったセッション管理モジュールになった場合、session_startを呼ぶ時に同じ事をすれば、同様におかしなセッションIDクッキーの有無が検出できます。今から同じコードにしたい場合は、session_startの直後にsession_regenerate_idを呼べば良いです。
どのプラットフォームのセッション管理でも同じ手法でおかしなクッキーを検出できます。
ブラウザが1つのクッキーだけを戻すようになりサーバ側の解釈により、クッキーの値が異なる、という状態はなくなりましたが、おかしなクッキーの検出は面倒になりました。
今の仕様の方が綺麗な感じはしますが、不便になりました。