strictなシステムに対するSession Fixation対策
2006/06/07 水曜日 - 11:10:25 by かなだSession Fixationについては話題になって久しいので今さら解説するまでもないと思う。……と思ったらそうでもなさそうなのでちょっと書いてみる。
Session Fixationとは何か
一般に、Session Fixationとして話題にのぼるのは、PHPなどにおける以下のような手法である。
http://www.example.com/index.php?PHPSESSID=abcde
こうすることで、(対策の取られていない)PHPは、セッションIDを’abcde’と解釈してしまう。セッションIDは本来サーバーが発行するはずのものであるが、URIに埋め込むことでクライアント側からセッションIDを指定することが出来てしまうものである。
この「セッションIDが任意のものに設定可能である」ことを利用した攻撃をSession Fixation攻撃と呼ぶ……と解釈している人は以下を引き続き読んで頂きたい。
permissiveとstrict
このPHPの脆弱性に対して、「URIからの指定でセッションIDが任意に指定できてしまう」点ばかりが大きくとりあげられる傾向にあるが(*1)、元凶は「送られてきたセッションIDがサーバーにない場合、そのセッションIDで新たにセッションを開始してしまう」という点だ。URI(GETメソッド)に限らず、POSTメソッドでもCookieを書き換えることでも任意の文字列をセッションIDに指定することは可能である。
このように、セッションIDを任意の文字列に設定可能であるシステムをpermissive(寛容)なシステム、これが通用しないものをstrict(厳格)なシステムと呼ぶ(*2)。最近「Session Fixation」というと、permissiveなシステムに対するものを指す場合が多いが、実はstrictなシステムにもSession Fixatonは生じうる。つまり、permissiveなシステムをstrictに変更しただけでは、本当のSession Fixation対策にはならないのだ。
Session Fixation対策として、
PHP Security Guide: Sessions
session_start();
if (!isset($_SESSION[’initiated’])) {
session_regenerate_id();
$_SESSION[’initiated’] = true;
}
のようなコードを散見する。これは一見、よくできた対策のように思える。URIに仕込んだ恣意的なセッションIDでは、$_SESSION[”initiated”]をtrueにすることは難しいため、seesion_regenerate_id()で生成された新しいセッションIDのみが取扱われるようになる。
しかしこれはpermissiveなシステムをstrictに近づける方策に過ぎない。この対策を取れば、システムがpermissiveであることにつけ込むSession Fixationは防げるが、strictなシステムに対するSession Fixationは依然として防げないのだ。
strictなシステムに対するSession Fixation
strictなシステムではセッションIDの偽造ができない。このためSession Fixation攻撃の可能性は極端に低くなるがゼロではない。それではstrictなシステムに対するSession Fixationとはどういったものだろうか。
例えば、認証を用いたシステムで、
認証以前のセッションIDを認証後も継続して利用する
認証状態が終了してもセッションIDを破棄しない
というWebアプリケーションが存在したとする。悪意のユーザAが自分のアカウントでログインする。当然セッションIDが発行されるが、この時のセッションIDをAは書き留めておく。そしてログアウトし、そのPCから去る。書き留めたセッションIDは、(2)により残ったままである。
その直後、善意のユーザBが同じPCに立ったとする。そしてログインする。この時、(1)によりAが書き留めたセッションIDが継続して利用されるため、Aは書き留めたセッションIDを使ってBに対してSession Hijackingを行うことができる。
こういったことは不可能に近いと思うかも知れない。しかし、ネットカフェのようなパブリックな場所にあるPCでは充分起こりうることだろう。
「これはSession Fixationではなく別の脆弱性だ」と思うかも知れない。しかし、Session Fixationは「ユーザーのセッションIDがログイン時以前に固定されている」ことが問題とされており、これもSession Fixationの一種と考えられる。(*3)
セッションに対する誤解がSession Fixationを生む
そもそもなぜSession Fixationが発生するのか。これはセッション管理の概念を誤解しているケースが多いのではないかと推測する。セッション管理とは、その名の通りある一連のセッションを維持するための仕組みである。そのため(一般的な)セッション管理では、セッション「開始時」にセッションIDというものを発行してUAに保持させる。そしてUA上のセッションIDを消失させた(あるいはUA側で破棄した)時点でセッションは事実上終了する(*4)。セッションIDはある一連のセッションを担保するキー(トークン)なのである。
認証を使うWebアプリケーションにおいては、認証完了(ログイン)からログアウトまでがセッションといえるだろう。これにセッション管理を利用するならば、セッションIDはログイン時に「新たに」発行され、ログアウト時に破棄されるべきである。こうすれば大抵の(認証に対する)Session Fixation脆弱性は起こらない。
しかし、ログアウトしてから次回にログインするまでも何らかの情報を保持しておきたい場合もあるだろう。こういった場合、ログイン時もログアウト時もセッションIDを破棄しないような実装になるだろう。
この実装の場合、「このセッションIDは何を指しているか」を考えてみよう。ログイン時に発行されるのでもなければ、ログアウト時に破棄されるわけでもない。つまりこのセッションIDは認証セッションを指しているのではない。認証セッションをまたいだ漫然と長い期間をセッションとしているのである。この間、他のユーザがログインする可能性も充分ある。このセッションIDに紐付けられた「セッション」は「ログイン中かどうか」とは全く関係ないのである。
つまり、こういったシステムの場合、「ログイン中かどうか」に関してはいわゆるセッション管理は全く行われていないことになる。Session Fixation攻撃とはこの「正しいセッション管理が行われていない部分」に対して行われる攻撃であると言える。strictなシステムでも使い方を間違えれば脆弱性を生むのは当然と言えよう。もちろん上述の’initiated’をtrueにするような対策では間に合わない。
ではどうすればいいのか
答えは簡単で、「ログイン中かどうか」を保証するセッションIDの代わりになるものをもう1つ用意すれば良い。つまり、セッションの中にもう一つセッションを作ってやれば良いのだ。コードは以下のようになるだろう。
// ======== 認証成功時 ========
$token = md5(uniqid(rand(), true)); // この文字列がログイン中を示すセッションIDの代わり
$_SESSION[’auth_key’] = $token;
$_COOKIE[’auth_key’] = $token;
// ======== ログイン中 ========
if( ! […]