セッションの危険性と対策~  365日の紙PHP(10日目)

近代的なPHP開発を行うために

おススメ!記事
Raspberry Pi 用「HAL」で、
カップラーメン・タイマーを作ってみよう!
ラズパイDIYの決定版! ソケットサーバー「HAL」をご紹介します。

さて、昨日はセッションを使ったログイン状態の保持について学びました。

セッションを使うとセッションIDが自動的に生成され、そのセッションで利用するデータがサーバーにファイルとして保存されます。セッションIDはcookieとしてユーザーのブラウザに保存されており、ユーザーが特に意識しなくともブラウザがセッションIDを自動で送信することで接続が維持されます。

このように非常に簡単なセッション機能なのですが、簡単過ぎる故に危険も隣り合わせです。

今日はセッションの危険性と、その対策について勉強します。とても重要な事な上、対策はとても簡単なのでよく読んで下さい。

セッションの危険性

セッションの危険性を知るため、2つのブラウザを使います。セッションはブラウザ単位で保存されているので別のブラウザが必要です。

ChromeかSafariを利用する場合は、それ以外を1つ用意して下さい。

まず、説明が分かりやすいようにするため、セッションIDがどんな文字列になっているか一目でわかるようにセッションIDを画面に出力してみましょう。index.php の if($login_ok === true){}文の後に echo session_id(); を挿入して下さい。

    if($login_ok === true){

$_SESSION["logged"] = true;
$_SESSION["user_id"] = $_POST["user_id"];

http_response_code(200);
header("Location: login.php");
}

echo session_id();

session_id() で、現在のセッションのセッションIDが取得できます。それを echo 文で出力しています。

altテキスト

このように画面上部にセッションIDが出力されます。login.php についても同様に echo 文を挿入しておいて下さい。

2種類のブラウザで index.php を開いてみてください。異なるセッションIDが表示されているはずです。

ブラウザのウィンドウを小さくして左右に並べましょう。左が あなた(被害者) 、右が 犯罪者 だと思って下さい。ChromeかSafariを使う方は犯罪者用として右に配置してください。

犯罪者が攻撃を開始

ではまず、犯罪者の立場です。犯罪者がログインできていないことを確認するために、右側の犯罪者のブラウザで login.php にアクセスしてみましょう。index.php にリダイレクトされるはずです。

確認できたら攻撃開始です。右側のブラウザのセッションIDをコピーし、以下のコードに埋め込んで下さい。

"><script>document.cookie="PHPSESSID=コピーした文字列"</script><span class="

余計なスペースなどが入らないようにしてください。

ここで犯罪者は何らかの手段を使って、上記の文字列を拡散します。

◯◯サイトの裏ワザ見つけた! ログイン画面でID欄に次の呪文を入力してログインするとポイント100倍!パスワードは適当でOK!

"><script>document.cookie="PHPSESSID=コピーした文字列"</script><span class="

被害者がトラップにひっかかる

さて、今度は被害者の立場になって左側のブラウザを操作します。左側のブラウザのセッションIDが何であるか確認しておいて下さい。

被害者は先程の攻撃者が拡散した情報を鵜呑みにしました。

拡散された情報をコピーし、ログイン画面 index.php のID欄にコピーアンドペーストし、適当なパスワードを入力して送信してみましょう。

当然、ログインができず index.php に戻されます。被害者は「? ガセネタだったか...」と思うことでしょう。この状態で、正しいIDとパスワードを入力してログインしてみてください。

ログイン画面の上部に表示されたセッションIDを見てみて下さい。犯罪者のセッションIDと同じになっているのが確認できましたか?

altテキスト

犯罪者がログインをスルー

と、いうことは右側の犯罪者のブラウザのアドレスバーに直接 login.php を入力してアクセスすると…

altテキスト

やはり、ログインできてしまいました。IDとパスワードを入力していないにもかかわらずです。ログインIDを確認すると左側のブラウザのアカウントでログインされていることがわかると思います。

あとは左側のブラウザのログインユーザーになりすまし、なんでもやりたい放題です。怖いですね。

対策

いかがでしょうか?

PHPでのセッションは自動で振り出されたセッションIDにより管理され、デフォルトではユーザーのブラウザのcookieにセッションIDを保存する仕組みになっています。

ですから、ユーザーのブラウザがcookieで申告してくるセッションIDのみを信用するため、今回のように悪意ある第三者がブラウザのセッションIDを改変してしまっても、それを信用してしまうのです。

このような攻撃をセッションの固定化、セッション・フィクセーション(session fixation)と呼びます。自分に割り振られたセッションIDを被害者のセッションIDとして固定化させたあと被害者にログインさせることで、自分はログイン認証を行うことなくログイン済みの状態にしてしまうのです。

こんなにも簡単にログイン認証が破られたら困りますよね?

どこが問題なのでしょう?

それは、攻撃者が設定したセッションIDが被害者のセッションIDに設定されてしまっていることです。被害者がログインしたら全く新しいセッションIDを生成して割り当ててしまえば良いだけですね。

そんな関数がPHPには当然存在します。

session_regenerate_id();

です。

これを宣言するだけで、全く新しいセッションIDがそのユーザーに割り当てられます。

session_regenerate_id()

通常は、この関数を

session_regenerate_id(true);

のように true を引数として宣言します。そうすると、この関数が呼ばれる前にユーザーに割り当てられていたセッションに保存されていたデータを破棄してくれます。古いセッションIDのときに保存されていた情報が悪用されることを防ぐことが出来ます。

プログラムコードはこうなります。

index.php

    if($login_ok === true){

session_regenerate_id(true);

$_SESSION["logged"] = true;
$_SESSION["user_id"] = $_POST["user_id"];

http_response_code(200);
header("Location: login.php");
exit;
}

さて、コードを修正したら先ほどと同じように攻撃者と被害者になってテストしてみましょう。

被害者でログインした画面で表示されるセッションIDが、攻撃者のセッションIDとは違ったものになっていて、攻撃者が login.php にアクセスしてもログインできず index.php にリダイレクトされる事が確認できるはずです。

最後に

このように、セッションを使った場合の攻撃もその対策も、とても簡単な事がお分かりいただけたでしょうか?

セッションは非常に便利な機能ですが、セッションを維持するためにユーザーのブラウザが申告するセッションIDを全面的に信用するという仕様にならざるを得ません。

ですからプログラムを開発する者としてはセッションが安全に運用できるような配慮を行う必要があります。

そのためには、ログインが成功したら必ず直ちに session_regenerate_id() を宣言し、全く新しいセッションIDをユーザーに割り当てる必要があります。

今日はセッションの安全性を確保する session_regenerate_id() を学びました。

明日は今回の攻撃で用いられたセッションIDの固定化そのものを阻止してしまう手段を勉強します。

お疲れ様でした。

この記事へのコメント

※現在コメントはMarkdown記法が強制です。>>Markdown の書き方


この記事に返信

このコメントに返信