セッションの仕組みは単純なキーの受け渡しとなっています。
session_startを呼んだ時点で、リクエストにセッションIDが含まれていたらそのIDを使用、セッションIDが無ければ新しく適当なセッションIDを作成します。
セッションIDは基本的にcookieに突っ込まれ、ブラウザへ返されます。
$_SESSIONに突っ込んだ値は、サーバ上のどこかに'sess_セッションID'のような名前のファイルとして保存されます。
再度アクセスがあり、そのときにリクエストにセッションIDがあればファイルに保存されたセッションファイルを読み出す、という仕組みです。
ここで問題となるのが、「リクエストにセッションIDが含まれていたらそのIDを使用」の部分です。
存在しないセッションIDを無理矢理くっつけた上でリクエストを出した場合、PHPはそのセッションをあっさりと受け入れてセッションを継続してしまいます。
これがセッション固定攻撃の原因です。
セッションは基本的にcookieに突っ込まれるのですが、携帯電話等cookieが保てないブラウザに対応するために、URLにセッションIDを含むことも出来ます。
session.use_trans_sidやuse_cookies等のディレクティブで設定できます。
デフォルトでは無効ですが、携帯電話用コンテンツを表示するために有効にしていた場合、
http://example.com/index.php?PHPSESSID=123456789abcdef
というリンクを踏んでしまったら、誰がアクセスしてもセッションIDが123456789abcdefだと判断されます。
123456789abcdefというセッションファイルが見つからなかった場合、PHPは新しくそのセッションIDでセッションを作成してしまいます。
これを利用すると、他者にセッションIDを付加したURLでアクセスさせ、自分でも同じURLでアクセスすると他者のセッションの中身を覗き見ることが可能になります。
session_fix.php
1
2
3
4
5
6
7
8
9
10
11
|
//セッション固定攻撃のシミュ
ini_set('session.use_trans_sid',1);
ini_set('session.use_cookies',0);
ini_set('session.use_only_cookies',0);
session_start();
if(isset($_REQUEST['param'])){$_SESSION['param']=$_REQUEST['param'];}
var_dump(session_id(),$_SESSION);
|
session_fix.php?param=hoge&PHPSESSID=123456789abcdef
でアクセスすると、セッションIDが'123456789abcdef'になっているのが確認できます。
ここで、別のPCから
session_fix.php?PHPSESSID=123456789abcdef
でアクセスすると、他人には見えてはならないはずの$_SESSION['param']の値が見えてしまいます。
これが例えばログイン情報をセッションで管理している会員サイトのようなシステムで起これば、自分はログインしていないのに他者のログインしたセッションを利用して会員サイトにアクセスできてしまいます。
直接$_SESSIONの中は覗けないので具体的なIDやパスワードが直接漏れるようなことが無いのが唯一の救いですが、パスワード再発行フロー等を利用してアカウントを乗っ取ったり、ディレクトリトラバーサル等別のセキュリティホールと組み合わせて個人情報を抜き取ったりといった攻撃も可能であるため、セッション固定攻撃自体を排除するようにしなければなりません。
このような場合のためにセッションIDを再発行することの出来るsession_regenerate_idが用意されています。
session_regenerate_idで新しいセッションを発行できますが、古い内容があった場合は削除せずにそのまま残してしまいます。
プログラム中でsession_regenerate_idを呼ぶとその場でセッションIDが変更されます。
アクセスしたユーザはその後は変更後のセッションIDでアクセスすることになるのですが、その前にアクセスしていたセッションIDが'123456789abcdef'の情報はそのままになっています。
その場合攻撃者は、古い内容ではありますが'123456789abcdef'のセッションの内容を取得できてしまいます。
PHP5.1以降ではsession_regenerate_id(true)で古いセッションを削除できるようになりました。
が、それ以前のバージョンでは、それまでのセッションを手動で削除しなければなりません。
session_destroy.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//PHPSSID有効化
ini_set('session.use_trans_sid',1);
ini_set('session.use_cookies',0);
ini_set('session.use_only_cookies',0);
//古いセッション削除
session_start();
$old_session=$_SESSION;
session_destroy();
//新しいセッション
session_start();
session_regenerate_id();
$_SESSION=$old_session;
|
session_destroy()で現在のセッションを全て削除することが出来ます。
セッション内容が消えてしまっては困るので、一旦変数に確保します。
ただしセッションID自体はそのままになっているので、session_regenerate_id()で変更します。
最後に確保しておいたセッション内容を新しいセッションに書き込めば、古いセッションを削除しつつ新しいセッションに移行することが出来ます。
他にセッションファイルを物理的に削除する方法などもありますが、session_destroy()を使用した方が手っ取り早いと思います。
まあsession_set_save_handler()でセッション乗っ取るのが一番なのでしょうけど。