8-1 クッキーとセッションへの攻撃と防御策 [PHPセキュリティー]
クッキーとセッション
クッキー
- クッキーはユーザーとサーバー間の状態を管理する機能を提供
HTTPプロトコルはユーザーとサーバー間の状態を管理してくれる機能はなく、ステートレスはプロトコル(通信方法、通信規約というようなものです)です。ウェブアプリケーションはユーザーとサーバー間の状態を管理してステートフルなサービスを提供する必要があります。そこで、クッキーが活躍します。
クッキー(Cookie)は、ユーザーとサーバー間の状態を管理する機能を提供します。HTTPプロトコルの拡張で、Set-Cookieレスポンスヘッダ と Cookieリクエストヘッダ という2つのHTTPヘッダから成り立ちます。クッキーは、サイトを訪れたユーザーのブラウザに文字列データを格納したり、再度サイトを訪問したユーザーを特定したりする仕組みのことです。
setcookie() か setrawcookie() を使用してクッキーをセットすることができます。PHPでクッキーデータを取得するにはCOOKIE変数である$_COOKIEを使用します。クッキーの詳細はこちらを参照下さい。
セッション
- セッションはサイトを訪れた個々のユーザーのデータを個別に管理する機能を提供
- セッションデータはセッションデータコンテナに格納される
- セッションデータは一意な識別子(ユニークなID)セッションIDによって管理される
- サーバー側のセッションIDとブラウザ側のクッキーに格納されたPHPSESSIDが一意な同一のIDであり、ユーザーを識別する
セッションはサイトを訪れた個々のユーザーのデータを個別に維持することによって、ユーザーとサーバー間の状態を維持管理する仕組みを提供します。
個々のユーザーのセッションで管理されたデータは、セッションデータコンテナに格納され、ユーザーのリクエスト毎に更新されます。セッションデータコンテナの中では、一意な識別子(ユニークなID)がセッションデータを管理しています。この識別子をセッションIDと言います。セッションデータコンテナに格納されるセッションIDとブラウザのクッキーに格納されているPHPSESSIDは一意となっており、ユーザーを識別します。サーバー側のセッションIDとブラウザ側のクッキーに格納されたPHPSESSIDが一意な同一のIDであり、ユーザーを識別し、状態を管理する仕組みを提供します。PHPでセッションデータを取得するにはSESSION変数である$_SESSIONを使用します。
PHPのセッションメカニズムは先天的なセキュリティー対策がないので、防護対策を準備する必要があります。
セッションの詳細はこちらを参照下さい。
セッションへの攻撃 セッションハイジャック
- セッションハイジャックはセッションデータの乗っ取り
- セッションIDの流出からセッションハイジャックへと発展
セッションハイジャックとはセッションデータが格納されているセッションコンテナの内容を不正に取得することです。セッション機構に基づいたサービスを乗っ取ることが出来る非常に危険な攻撃です。
セッションハイジャックを成功させるために、攻撃者は様々なアプローチを行います。ブラウザの脆弱性を突いてクッキーデータを盗んだり、攻撃者が用意したセッションIDを正規ユーザーに使用させたり、ネットワーク上のhttpプロトコル通信を盗聴したりして、セッションIDを取得し、セッションハイジャックへと発展させます。
セッションハイジャックはセッションIDの流出により発生します。セッションIDを守ることがセッションハイジャックを防ぐ有効な手段となります。
クッキーデータの流出によるセッションIDの流出
- クッキー流出によるセッションデータの流出
- セッションIDがクッキーの中に保存されているのでクッキーが盗まれることでセッションハイジャック(乗っ取り)に発展
ユーザーのブラウザに格納されているクッキーが攻撃者に盗まれる場合があります。その場合、セッションIDがクッキーの中に保存されているので、セッションハイジャック(乗っ取り)に発展する可能性が発生します。
クッキーに格納されたPHPSESSIDが指定しているIDは、サーバー側のセッションIDと同一であり、セッションIDはセッションデータコンテナの内容を取得するIDなので、セッションIDが提供するデータに基づくサービスを取得される場合があります。
セッション固定化攻撃によるセッションIDの流出
- セッション固定化攻撃は攻撃者が用意したIDを正規ユーザーに使用させること
固定化は、攻撃者が予め用意しておいたセッションIDを使用させることで、正規ユーザーのセッションIDを取得します。
もっとも簡単なセッション固定化攻撃ではリンクを使用します。
攻撃者は以下のようなリンクを対象者にクリックさせます。
1 | <a href="https://wepicks.net/session.php?PHPSESSID=1234abcd">Click here</a> |
例えば、セッション機能を使用しているサイトがあるとします。そのサイトのアドレスが http://www.example.com/ だとします。攻撃者は攻撃対象ユーザーに以下のURLが記載されたメールを送信してクリックさせます。
1 | <a href="http://www.example.com/?PHPSESSID=1234abcd">Click here</a> |
これによりセッションIDが 1234abcd となります。攻撃対象ユーザーがこのサイトにログインするとセッションIDが 1234abcd となります。攻撃者もセッションIDを 1234abcd に指定してサイトにアクセスすることで、攻撃対象ユーザーのアカウントを乗っ取ることが出来ます。
盗聴によるセッションIDの流出
セッションハイジャックの防御策
セッションのセキュリティーを維持するために最も大切なことはセッションIDが流出しないようにすることです。セッションIDの秘密を守ること、正規のセッションIDを使用することで、セッションへの攻撃を防ぐことが出来ます。また、セッションデータを暗号化したり、独自にセッションの保存や読み込み機能を作成する方法が有効です。
画面遷移でトークンを使用
- ランダムな文字列でトークンを作成してURLで伝播させてユーザーを追跡
ユーザーの画面遷移のURLの中でトークンを伝搬させます。トークンをランダムな文字列を使用して作成し、すべてのリンクに追加します。
URLでトークンを伝播し、クッキーでセッションIDを伝播し、その両方をランダムな一意な文字列としてユーザーを識別して追跡します。
両方の用意することがセッションハイジャックに対する防御となります。
reg1.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php //ユーザー情報を元にに token を作成 $string = $_SERVER[HTTP_USER_AGENT]; $string .= 'TRYPHP'; $tokenUrl = md5($string); $_SESSION['tokenUrl'] = $tokenUrl; //ランダムな文字列で token を作成 $tokenUrl = md5(uniqid(rand(), TRUE)); $_SESSION['tokenUrl'] = $tokenUrl; //配列変数初期化 $url = array(); $html = array(); //URLエンコード $url['tokenUrl'] = rawurlencode($tokenUrl); //htmlエスケープ $html['tokenUrl'] = htmlentities($url['tokenUrl'], ENT_QUOTES, 'UTF-8'); //リンク print "<a href='reg2.php?tokenUrl=".$html['token_url']."'>reg2.php</a>"; ?> |
reg2.php
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?php //配列変数初期化 $cln = array(); //フィルタ後初期化した変数に格納 $cln['tokenUrl'] = $_GET['tokenUrl']; //URLトークンとセッショントークンを比較 if($cln['tokenUrl'] != $_SESSION['tokenUrl']){ //処理停止 exit; } ?> |
SSLの導入や最新のブラウザを使用
- SSLの導入や最新のブラウザを使用
SSLを導入して通信を暗号化したり、最新のブラウザの使用を促すことで、ブラウザの脆弱性を防ぐことで、セッションIDの流出を回避することが出来ます。
セッションIDを再発行することで回避
- セッションIDを再発行することで回避
セッションID固定化攻撃を防ぐために、ユーザーがログインしたり、これまでと違う新たな権限を獲得したりするタイミングで、セッションIDを新規に発行することでです。
PHPではセッションIDを再発行する関数がありますので、認証する前に session_regenerate_id()関数 を実行します。
1 2 3 4 5 6 7 8 9 10 | <?php $_SESSION['login'] = FALSE; if(auth()){ //認証がTRUEになる前にセッションIDを再発行する。 session_regenerate_id(); //新たなセッションIDによる管理が行われます。 $_SESSION['login'] = TRUE; } ?> |
セッションデータの暗号化
- セッションデータの暗号化
セッションデータを暗号化して適切な方法以外では読み出せなくする対策が有効です。セッションデータの読み書きを独自な方法で行い、書き込みを暗号化し、読み出しを複合化する仕組みを作ることで回避できます。
session_set_save_handler()関数で独自の書き込みと読み出しを行う
- session_set_save_handler()関数で独自の書き込みと読み出しを行う
テーブルの作成
1 2 3 4 5 6 7 | //テーブルの作成 create table sessions( id varchar(32) NOT NULL, access int(10) unsigned, data text, PRIMARY KEY (id) ); |
セッションデータをデータベースに格納する関数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | <?php //1 open function _open(){ //変数取り込み global $sqlServer; global $dbName; global $dbUser; global $dbPass; $dbConnect = mysql_connect($sqlServer,$dbUser,$dbPass); if($dbConnect){ return mysql_select_db($dbName,$dbConnect); } else{ return false; } } //2 close function _close(){ //変数取り込み global $sqlServer; global $dbName; global $dbUser; global $dbPass; $dbConnect = mysql_connect($sqlServer,$dbUser,$dbPass); return mysql_close($dbConnect); } //3 read function _read($id){ //変数取り込み global $sqlServer; global $dbName; global $dbUser; global $dbPass; $dbConnect = mysql_connect($sqlServer,$dbUser,$dbPass); mysql_select_db($dbName,$dbConnect); $id = mysql_real_escape_string($id); $sql = "select data from sessions where id = '$id'"; if($result = mysql_query($sql, $dbConnect)){ if(mysql_num_rows($result)){ $rows = mysql_fetch_array($result); return $rows['data']; } } return ''; } //4 write function _write($id, $data){ //変数取り込み global $sqlServer; global $dbName; global $dbUser; global $dbPass; $dbConnect = mysql_connect($sqlServer,$dbUser,$dbPass); mysql_select_db($dbName,$dbConnect); $access = time(); $id = mysql_real_escape_string($id); $access = mysql_real_escape_string($access); $data = mysql_real_escape_string($data); $sql = "replace into sessions values ('$id', '$access', '$data')"; return mysql_query($sql, $dbConnect); } //5 destroy function _destroy($id){ //変数取り込み global $sqlServer; global $dbName; global $dbUser; global $dbPass; $dbConnect = mysql_connect($sqlServer,$dbUser,$dbPass); mysql_select_db($dbName,$dbConnect); $id = mysql_real_escape_string($id); $sql = ""delete from sessions where id = '$id'""; return mysql_query($sql, $dbConnect); } //6 clean function _clean($max){ //変数取り込み global $sqlServer; global $dbName; global $dbUser; global $dbPass; $dbConnect = mysql_connect($sqlServer,$dbUser,$dbPass); mysql_select_db($dbName,$dbConnect); $old = time() - $max; $old = mysql_real_escape_string($old); $sql = "delete from sessions where access < '$old'"; return mysql_query($sql, $dbConnect); } ?> |
SESSIONの呼び出し
1 2 3 4 5 6 7 8 9 | session_set_save_handler( '_open', '_close', '_read', '_write', '_destroy', '_clean' ); session_start(); |
タグ(=記事関連ワード)
日付
最終更新日:2017年08月09日