2013年8月26日月曜日

PHP5.5.2以降のstrict sessionsモードでセッションフィクセイション対策はどうすればよいか

先日のエントリ『【速報】PHP-5.5.2にて大垣さんのstrict sessionsが実装されました』にて、PHP5.5.2でセッションアダプションが解消されたことを報告しました(session.use_strict_mode=1の場合)。
セッションアダプションとは、未初期化のセッションID(たとえばPHPSESSID=ABC)をPHPが受け入れる問題のことです。strict sessionsを使用すると、PHPが生成し、現在有効であるセッションIDのみを受け入れ、そうでない場合、PHPはセッションIDを振り直します。

あいにくPHP5.5.2(PHP5.5.3も)にはバグがあり、session.use_strict_mode=1によるstrict sessionsは使用できませんが、既に大垣さん自身によりバグ修正されているので、PHP5.5.4からは使えるようになるでしょう。

strict sessionsの主な目的は、セッションフィクセイション攻撃に対する影響緩和ですが、では、strict sessionsを用いると、セッションフィクセイション対策はどのように変わるでしょうか?

従来のPHP、すなわちセッションアダプションがある状態では、セッションフィクセイション対策は下記の通りでした。
  • ログイン直後にsession_regenerate_id関数によりセッションIDを振り直す
これに対して、セッションアダプションがない状態ではどうでしょうか。先に『セッションアダプションがなくてもセッションフィクセイション攻撃は可能』で説明したとおり、攻撃者が、対象サイトのセッションIDを取得することにより、セッションフィクセイション攻撃は可能です。従って、strict sessionsの状況でも、セッションフィクセイション対策は下記の通りです。
  • ログイン直後にsession_regenerate_id関数によりセッションIDを振り直す
何も変わらないわけです。

…とここで終わりにしてもよいのですが、せっかく大垣さん8年越しのstrict sessionsが使えるようになったので、セッションIDの振り直しをしないでセッションフィクセイション対策ができないか考えてみましょう。

先に説明したように、セッションアダプションがない状況では、攻撃者は、対象サイトから有効なセッションIDを取得する必要があります。ここに着目し、「攻撃に使えるセッションIDを攻撃者に渡さない」アプローチを考えてみます。セッションアダプションがなく、セッションIDがとれない状況では、セッションフィクセイション攻撃はできません。

まず、上記がだめな状況として、ログイン状況でなくてもセッションを使っているサイトが挙げられます。この場合、サイトにアクセスさえすれば有効なセッションIDが取得できるため、セッションフィクセイション攻撃が可能になります。このため、以下では、セッションはログイン状態でのみ使用しているという前提とします。

以下、ログイン状態でのみセッションを使っているアプリケーションを想定して、セッションIDの振り直しをしないでセッションフィクセイション対策する方法を検討します。以下の処理毎に検討します。
  • ログイン前
  • ログイン処理
  • ログイン状態の確認処理
  • ログアウト処理

ログイン前

前述のように、ログイン前にセッションを有効にすると、そのセッションIDを悪用してセッションフィクセイション攻撃ができてしまいます。このため、ログイン前にはセッションは使えません。これによる副作用として、ログイン時にCSRF対策したい場合でも、それが困難になります。CSRFの標準的な対策にはセッションを利用するからです。

ログイン処理

ログイン処理の注意として以下があります。
  • ログインに失敗した場合は、セッションID悪用防止のためセッションを破棄する
  • 元々ログイン状態の場合は、ログイン処理を継続しない。攻撃者のログイン状態のセッションIDによる攻撃を防ぐため
これを実現する擬似コード例を下記に示します。
session_start();
if (ログイン済みの状態) {
  echo "ログイン済みです";
  // マイページなど、遷移可能なページへのリンクを表示
  // あるいは、異常事態としてセッション破棄するという考え方もあり
} else if (idとパスワードが有効) {
  // ログイン処理
  $_SESSION['user'] = $id;
  // その他の処理
} else {
  echo "IDまたはパスワードが違います";
  // 認証失敗の場合はセッションを破棄
  session_destroy();
}
重要なポイントとしては、(1)ログイン済みなのに他のユーザでログインすることを許さない、(2)認証に失敗した場合は必ずセッションを破棄する、ということがあげられます。
また、懸念点として、ログイン処理中にアプリケーションが異常終了すると、session_destroyが呼び出されず、有効なセッションが残ってしまう可能性があります。対策は、セッションを有効にする区間をできるだけ短くすることですが、詳しい説明を割愛します。

ログイン状態の確認処理

ログイン前の攻撃者がセッションIDを取得する方法として、攻撃者が、認証の必要なページにアクセスするというものがあります。
典型的なログイン確認は以下の通りです。
session_start();
if (! isset($_SESSION['user'])) {
  echo 'ログインしてください';
  // ログインページへのリンクを表示
  exit;
}
これだと、セッションIDは生成され、セッションは有効なままなので、攻撃者は有効なセッションIDを取得できます。これを防ぐには、exitする前にセッションを破棄します。
session_destroy();
これにより、セッションは有効でなくなるため、セッションフィクセイション攻撃に使えるセッションIDは取得できなくなります。この処理は、ログイン状態を確認するページ全てで、もれなく実装する必要があります。

ログアウト処理

ログアウト処理において重要なポイントは、かならずセッションを破棄するということです。そうしないと、ログアウト後に残ったセッションのセッションIDを用いて、セッションフィクセイション攻撃ができてしまいます。
具体的には、下記のようなログアウト処理はだめだと言うことです。
$_SESSION['user'] = false; // 認証ユーザをfalseにすることでログアウトとする

アプリ側のセッションタイムアウトに対する注意

アプリケーション仕様によっては、PHPのセッションは有効だが、アプリ側で定めたセッションタイムアウトになっているという状況が考えられます。この場合、「ログアウト状態だがセッションIDは有効」という状態になり、セッションフィクセイション攻撃に使えるセッションIDができてしまいます。
この対策としては、下記が考えられます。
  • ログイン状態の確認処理の中で、タイムアウトしたセッションを破棄する
  • ログイン処理においては、タイムアウトしたセッションは、いったん破棄して、再度セッションを開始する。
ログイン状態の確認…については、前述の処理でカバーされているとも考えられますが、ログイン処理の方は特に注意が必要です。

一方、PHP処理系側でタイムアウトしたセッションは、既に破棄されているため、アプリケーション側で特に注意することはありません。

まとめ

strict sessionsにおいても、セッションフィクセイション攻撃対策として、認証成功直後のsession_regenerate_idは必須ですが、敢えてこれをしないで、セッションフィクセイション攻撃対策する方法を検討し、以下が必要であることを示しました。
  • ログイン前にはセッションを使用しない
  • ログインに失敗した場合は、セッションを破棄する
  • 元々ログイン状態の場合は、ログイン処理を継続しない
  • ログイン状態の確認において、ログイン状態でない場合はセッションを破棄する
  • ログアウト処理ではセッションを破棄する
  • ログイン状態の確認処理の中で、タイムアウトしたセッションを破棄する
  • ログイン処理においては、タイムアウトしたセッションは、いったん破棄して、再度セッションを開始する
ご覧のように、strict sessionsによってセッションフィクセイション脆弱性を気にしないですむどころか、至る所でセッションフィクセイション脆弱性に配慮しなければならないことがわかりました。上記の1カ所でも漏れると、セッションフィクセイション脆弱性の可能性が生じます。

したがって、strict sessionsにおいても、下記を推奨します。これだと、セッションフィクセイション対策は、ログイン処理1カ所に集約できます。
  • ログイン直後にsession_regenerate_id関数によりセッションIDを振り直す
ということで、strict sessionsにおいても、アプリケーションの書き方は変わらない、というのが結論です。

0 件のコメント:

コメントを投稿

フォロワー