システム奮闘記:その62

セッション管理、クッキー(cookie)入門



Tweet

(2007年9月4日に掲載)
はじめに

  クッキー(cookie)、セッション管理とは何か?

  よく見かける用語なのだが・・・

  ほとんど理解してませーん (^^)

  だった。

  というわけで、今回は、クッキー(cookie)とセッション管理の話を
取り上げたいと思います。

クッキー(cookie)とは何か?

まずはクッキーから。 「クッキー」(cookie)という用語は、以前から知っていた。 会員制のチケット販売、mixiなどのホームページを見る場合、 IDとパスワードの認証を行う必要がある。
mixiの認証画面
mixiの認証画面
この先に進む場合、自分のIDとパスワードを入力する必要がある。
mixiの場合、IDは登録しているメールアドレスで、
パスワードは自分で決めた秘密の呪文だ。

この2つを入力すると、mixiのサーバーに送られる。
図にすると以下の通りになる。
IDとパスワードの送信の図

  だが、2回目以降、mixiの認証画面に出くわすと、
過去に入力したID、パスワードが表示される。

mixiの認証画面
2回目以降のmixiの認証画面
既にIDとパスワードが入力された状態の画面が表示される。
この仕掛けのお陰で、毎回、ID、パスワードを入力する手間が省ける。

  だが、もっと便利な方法がある。

mixiの認証画面
自動ログインの印
上のピンクで囲んだ部分に印をつけておくと、
自動的にログインしてくれるのだ。
これだと、わざわざ「ログイン」ボタンを押す手間も省ける。
ここまで横着ができるのだ (^^)

図にすると以下の通りになる。
ID、パスワードが不要でログイン可能

  しかし、どういう仕組みなのかは、理解していなかった。
  それから「クッキーはブラウザが食べる」という話を知った。
  確かに、クッキーの綴りは「cookie」なので、あのお菓子のクッキーの事だ。

  ブラウザがクッキーを食べるとは、どういう事なのか。

ブラウザがクッキー(cookie)を食べる
ブラウザがクッキ(cookie)を覚える絵
ブラウザ自身が入力されたIDやパスワードを覚える事だ。
その覚える事を「食べる」という表現を使っている。

クッキーの綴りは「cookie」で、お菓子のクッキーと同じだ。
恐らくブラウザに記憶させる物(食べさせる)という事から
クッキーと名付けたと思われる。

  クッキー(cookie)を食べたブラウザ。
  ブラウザ自身がID、パスワードを覚えているので、以下の事が可能になる。

食べたクッキー(cookie)を活用する
クッキー(cookie)を覚えさせた結果
ブラウザ自身が覚えたIDやパスワードを活用する事によって
過去にログインした所に、ログインする場合、ID、パスワードの
入力が不要になる。

  だが、クッキー(cookie)に関しては、この程度の知識しか持っていなかった。

  そして、クッキー(cookie)に関心も全くなかったので、勉強する気もなく
長い間、この程度の知識で過ごしていた。

セッション管理とは何か?

さて、セッション管理とは何か。Web技術で使われている物だ。 概略を説明する前に、Web接続の形態を考えてみる。
Webの接続形態を考える
Webの接続形態はクライアントは要求を出して、サーバーは応答を返すだけ
クライアントは、サーバーに対して、ホームページのデータを要求する。
サーバーは、クライアントの要求を受け、データを送る

この時、クライアントはデータの送信要求だけを行い、
特に、私は「**のIDを持った者よ!」と名乗らないため
サーバーは、クライアントが何かを考える事なく、
ただ、要求されたデータを送り返すだけなのだ。

  クライアントを特定する必要のない場合は、問題はない。
  でも、引続き、やりとりを行う場合、クライアントを特定できないため
そのため以下のような事になる。

Webの接続は「一期一会」
一期一会の仕組みになっている
クライアント「mayumi」が2度目以降の接続する時も、
「mayumi」である事を特定する物がないため、
初回に接続した時と同じ対応を行う。

そのため、クライアントが「私、さっき接続したよ」と言っても
サーバーは「そんなもん、覚えてへんわ」となる。

  そう、まさに・・・

  Webは一期一会なのらー!!

  余談になるが「一期一会」という言葉。「いちごいちえ」と読むのだが
どういうわけか、私は「いちごんいちえ」と間違えて覚えていたため、
漢字変換ができずに悩んでしまった。本を見て、自分の間違えに気づく (^^;;


  さて、Webが一期一会だと、どういう問題が起こるのか。
   
  ネット販売などで、IDとパスワードを入力して、認証が完了しても
商品を選ぶ際、清算する場合など、クライアントがWeb表示のための
データを要求する度に、サーバーは「あんた、誰やねん」という事で、
IDとパスワードを聞き、認証を行う必要が出てくる。

  一期一会だと非常に手間なのらー!

  だが、実際のネット販売や航空券の購入のサイトなどは
商品を選んだり、清算する度に、認証を要求してこない。

  それは何故か?

  一期一会にならないように、最初にクライアントが接続した時、
サーバーは特定のID「セッションID」を発行して、接続元を認識する
仕掛けになっている。

サーバーは特定のID「セッションID」を発行する
接続元の特定のためサーバーはセッションIDを発行
クライアントが最初に接続した時、サーバーは接続元のクライアントを
特定するためのIDを発行する。これを「セッションID」と呼ぶ。

  サーバーが「セッションID」を発行する事で、接続元を特定する事ができる。
  そして以下のような事が可能になる。

「セッションID」を紐付けし、クライアントの情報を保管
サーバー上のデータとセッションIDを紐付けする事で特定の接続元との通信が可能になる
接続元が送ったデータを「セッションID」と紐付けして
サーバー内部に保管する事で、クライアントとのやりとりが記録される。
記録されたデータを元に、クライアントとやりとりを行えば、
一期一会ではなく、連続した会話(データのやりとり)が可能になる。

クライアント・サーバー間で接続や情報の管理、保持する事を
セッション管理と呼ぶ。

  人間の会話でもそうなのだ。
  相手が誰かを認識した上で、会話の内容を記憶する事によって
色々な会話に発展していく。
  しかし、相手が誰か特定せずに、発言した内容だけを記憶しても、
誰が話したのかわからないため、相手が次の発言をしても、
同じ相手が連続して話している内容かどうかがわからず、
連続した会話が成り立たなくなる。

  コンピューターの場合、相手を認識するための道具として
「セッションID」があるのだ。

クライアントとサーバーは連続した会話ができる
セッションIDのおかげで、一期一会の通信ではなく、連続した対話が可能になる
「セッションID」のお陰で、一期一会ではなく
上図のように連続した通信のやりとりが可能になる。

  上図のように、最初の接続か、2回目以降の接続かが判断可能になる。
  これを応用した例が、認証が必要なサイトだ。

何度も認証を行う必要がなくなる
セッションIDで特定できるため何度も認証する必要がなくなる
最初の認証時に、セッションIDと認証許可の情報を紐付けしておけば
2回目からはセッションIDをサーバーが確認するだけで、
認証を行う必要がなくなる。

  わかったような、わからんような書き方になってしまった。

  そこで具体的な例を示すため、全日空のサイトを見てみる事にした。

全日空の航空券の予約・照会サイトを見てみる
全日空のサイトのログイン画面
マイレージ会員の場合、お客様番号(ユーザーID)とパスワードを入力して
ユーザー認証を行う。
ユーザー認証後の内容
ログイン後の画面
私の個人情報(氏名・マイレージ)が表示される(ピンクの部分)
これはお客様IDと紐づけされている私の個人データだ。

もし、このサイトが一期一会なら、この先、航空券の予約を行う場合も
空席照会を行う場合でも、次の内容を見る度に認証を行う必要がある。

だが、実際に予約照会をする場合でも、認証を行う必要はない。
全日空の航空券の予約照会を選ぶ
予約照会の選択
全日空の航空券の予約照会の中身
予約照会画面
上のように各ページを見る度に認証を行う必要はない。

それは、最初に認証を行った時点(もしくは認証を行う前)に、
全日空のサイトからセッションIDを発行され、認証成功後、
発行された「セッションID」は認証済みと記録されているため
上のように予約照会や、空席照会などを見ても、認証は不要になる。

  さて、全日空のサイトを紹介しているため、全日空から
便宜を図ってもらっていると邪推される方、堂々と反論します。
  私の方が・・・

  宣伝するから航空券を頂戴 (^^)

  と便宜を要求したいのだ (^^)

  というわけで、全日空の関係者の方で、ご覧になられた方は
「ヨーロッパ往復の航空券を頂戴ね」と露骨にタカってみる (^^)
  もちろん、日本航空の方で航空券をくださるのであれば、
喜んで日本航空のサイトを紹介します♪


  閑話休題。
  さて、クライアント・サーバーの間で通信や情報を管理・保持する事を
セッション管理と呼び、その管理を行う上で必要なIDの事を
セッションIDと呼ぶ事を書きました。

  今まで無意識に「セッション」と書いていたが、よく考えると

  セッションって何やねん?

  なのだ。
  コンピューター用語を、何も考えずに丸暗記していたら
言葉を覚えるだけでなく、概念を理解するにも時間がかかる。
  そこで「セッション」(session)を辞書で調べてみる事にした。

「セッション」(session)の意味
英和辞典で「session」を引くと、会議、会合の意味がある。
集まり、集団活動の意味もある。
何かをするための時間、期間という意味もある。
その意味は、辞書を見て初めて知った。
セミナーの「セッション」は、そこから由来している事が納得できる。

恐らくセッション管理のセッションは、クライアントとサーバーとの
接続期間の意味で「session」を使っていると思われる。

  「セッション管理」や「セッション」というカタカナから入ると
意味がわからない上、構えてしまうが、語源を調べてみると
すんなり入れる。うーん、そんな事に気づいていれば、もっとIT関連の
学習が楽だったのにと後悔 (^^;;


  ところでセッション管理で、管理するセッションIDとは、どんなIDで、
どんな文字列を使えば良いのか。

  結論から書けば、個々の接続が特定できる物であれば
何でも良いので、これといった決まりはない。

  個々の接続が特定できれば良いので、接続時間をIDに使う事も可能だ。
  UNIX時間(1970年1月1日から現在までに経過した秒数)でも良いし、
時間で作った物だと知られたくない場合は、それにハッシュ関数を使えば
特定可能な文字列のIDにする事もできる。

セッションIDの発行
セッション管理とセッションIDの発行
セッション管理を行う場合、サーバー側から個々の接続を特定する
セッションIDを発行する。

  さて、PHP言語では、どんなセッションIDを発行するのか。
  まずは、PHP3の時代は、PHPにセッション管理を行う関数がなかったため
PHPLIBと呼ばれる物を使って、セッション管理を行っていた。

PHP3 + PHPLIBを使ったセッション管理
PHP3時代のセッション管理の仕組み
PHPLIBというライブラリがセッション管理を行う。
このライブラリがセッションIDを発行する。

  PHPLIBにはお世話になった。

  2003年、PHP3でネット販売システムを構築した時、セッション管理をするため
PHPLIBを使ったのだ。

  さて、PHP4以降ではPHPの標準関数にセッションに関する関数がある。
  この時、PHP4が主流になりつつあったが・・・

  PHP4は難しいと思って手を出さなかったのらー!!

  なのだ (^^)

  そのため、PHP3 + PHPLIBでセッション管理を使ったのだ。


  ところで、PHP3 + PHPLIB の場合、セッションIDは、すぐにわかる場所にある。
  自動的に、URLの後ろに付加されるのだ。

PHP3 + PHPLIB の場合のセッションID
PHP3時代のセッションIDはURLに付加される
URLの後ろに自動的にセッションIDを付加させる事によって、
クライアントは、いつでもサーバーから割り当てられた
セッションIDを送信する事ができる。

  だが、色々なサイトを見ていると、URLの後ろにセッションIDを付加させる
サイトなんぞ、ほとんど見ない。
  さて、認証成功後の全日空のサイトのURLを見てみる。

認証成功後の全日空のサイト
全日空のサイトのURLの表示
URLの後ろには何もついていない。
もちろん、セッションIDなんぞついていない。

  セッションIDは、ブラウザのどこに保管しているのだろうか。

  2006年にPHP4言語でセッション管理を使ってみようと思った事があった。
  セッションIDは、どこに覚えさせているのかで、URLの後ろでもないし、
  HTMLの記述で、<input type="hidden">を使った方法で
セッションIDを覚えさせているわけでもない。
  どこにセッションIDを覚えさせているか見当がつかない。

  この時点では、セッション管理がクッキー(cookie)を応用した物だと知らなかった。
  なので、セッション管理とクッキー(cookie)が結び付いているとは考えもしなかった。

  もしかしてクライアント側にはセッションIDを伝えずに、
サーバー側で保管しているのだろうか。
  でも、その場合だと、どうやって個々の接続を認識するのだろうか。
  何か特別な魔法でも使っているのだろうか。

  そこで以下の本で、セッション管理の部分を読んでみた。
  「改訂新版 基礎PHP」(WINGSプロジェクト:インプレス)

  だが・・・

  わからぬのらー!!

  という事で、PHP4のセッション関数の導入をアッサリと断念した。
  自分の理解できない物は使わないと言えば、ちょっとは格好がつくが
自分の理解できない物は「使い方すら理解できない」ので、 
PHP4以降に標準で搭載されるようになったセッション関数の導入は断念した。

  もっとも盗まれても支障がなかったセッションIDのため、
UNIX時間を使ったセッションIDの発行という事で、サーバーで発行し、
HTMLの記述に<input type="hidden">を使って覚えさせる方法で逃げる事にした。
 

買い物かごで覚えたセッション管理の仕組み

さて、セッション管理を使っているWebシステムで、どうやって セッションIDを保管しているのか。わからないまま放置し続けていた。 そんな状態だったのだが、転機が訪れた。 2007年3月に、ネット販売システムの再構築が決まった。 そして、ふと次の事が頭を過った。 セッション管理の仕組みを学習せねば (^^;; 重い腰をあげようと思った矢先、mixiのワイン会でお世話になっている 西宮市の鳴尾のピンチョスのご主人のkazさんから次の依頼がきた。 ピンチョスの店舗のサイトがないので紹介のページにリンクを張りました。 グルメウォーカーのピンチョスの記事 ネット販売で、買い物カゴ(カート)を提供するサイトを構築する事が できないかという話だった。 色々、話をしながら、kazさんが、どんなシステムを考えられているのかを 図にしてみた。以下のような想像図になった。
kazさんが思い描いている内容
思い描いていたネット販売の仕組み
カートを提供するサーバーと、商品を展示するサーバーは別物にする。

商品を展示する側は、システムの知識がなくても良いように
HTML言語のみにする。

  これを見て、難しいなぁと思った。
  だが、私自身、勉強になるため、可能な限り、引き受ける事にした。

  さて、家に帰り、どうやって実現させるのかを考える。
  そしてkazさんとの話の内容を整理していく事にした。


  購入者が物を購入する際に、購入者を識別する必要がある。

個々の接続を識別するのは
個々の接続元を特定する必要性
購入者が物を購入する際、商品と顧客情報とを紐付けしておかないと
誰が購入したのかわからなくなる。

もちろん、同じ顧客であっても、昨日は大根を購入したが
今日は白菜を買うという感じで、毎回、購入する物も違う。
そのため、その時、その時の接続情報が必要になる。

  その時、その時の接続情報と言えば、セッションIDとなる。
  そこで、セッション管理の事を復習してみる。

個々の接続を識別するのは
セッションIDを使えば特定できる
最初の接続の際、セッションIDはサーバーから与えられる。

セッションIDを使えば、その時、その時の接続を識別できるため、
購入に関する情報と紐付けできる。

それに、セッションIDと各種データを紐付けできるため、
各種データをサーバー側に保管できるので、わざわざ、
クライアントにデータを送り、<input type="hidden">で覚えさせ、
返答させるような事もなくなる。

  そして、クライアントはセッションIDを保持する。

クライアントはセッションIDを保持する
クライアントはサーバーからもらったセッションIDを保持
なんらかの形で、クライアントはサーバーから与えられた
セッションIDを保持する。

  この復習を行った時点では、セッション管理はクッキー(cookie)を使っていると
知らなかったため、クライアントがセッションIDを保管する方法として
「なんらかの形」と思った。

  そして、2回目以降のサーバーへの接続要求から、クライアントは
サーバーへセッションIDを送る。

2回目以降はサーバーへセッションIDを送る
2回目以降の接続はセッションIDをサーバーへ送る
クライアントはサーバーから与えられたセッションIDを使い
サーバーが現在の接続を識別できるようにする。

  この仕組みによって、個々の接続に対して、クライアントとサーバーは
個別の対応が可能になるのだ。


  復習が終わった所で、セッションIDがどのように保管されているかを
いくら考えた所で、わからないと思ったので、とりあえずは、
セッション管理を使った物がどんな物なのかを知るため、
PHP言語でプログラムをしてみる事にした。

  そこで以下の本を取り出す。
  「PHPによるWebアプリケーション スーパーサンプル 第2版」
  (西沢直木:ソフトバンククリエイティブ)

  分厚いPHP言語のプログラム集だ。

  以前なら

  それでも私の三段腹の方が分厚いのらー (^^)

  と書く所だが、2007年7月20日時点での体重が62キロ。
  2004年時点では80キロ以上あり、信楽焼きのタヌキのお腹をしていたのだが、
運動不足解消、メタボ対策のため、運動や減量をして20キロも痩せたため、
かつてのように、三段腹の形容詞が使えない (^^;;


  さて、分厚いプログラム集を見ていく事にした。

  セッションIDとは何かが書いてあった。
  「この接続は誰だ」と示すためのIDなのだ。
  そして、このセッションIDに紐づけされた形でサーバー側に保管される
変数の事をセッション変数という。

セッションIDとセッション変数について
セッションIDとセッション変数について
セッションIDは、その接続は誰が行っているのか特定する物。

セッション変数は、その接続を行っている人物に関連する情報を
保管するための変数だ。ネット販売なら購入商品や個数を入れたりする。

セッション変数は、セッションIDと紐づけされるため、
セッションIDがあれば、個々の接続の際に保管したデータを
取り出す事ができる。

  セッションIDをブラウザに覚えさせる方法が2つあると書いていた。
  その内容を以下の内容だった。

セッションIDをブラウザに覚えさせる方法
(1)
クッキー(cookie)を使う。

URLの後ろにセッションIDを付けると、
セッションIDが丸見えのため、セキュリティー上、良くない。
クッキー(cookie)を使うと表に出ない上、お気に入りに入れても
URLにセッションIDが残らないため、重複利用が防止できる。
(2)
URLの後ろのセッションIDをつける。

クッキー(cookie)が使えない携帯等では、この方法が使われる事がある。
ただし、ブックマークされるとセッションIDも
URLに記録されるため重複利用が起こる上、
セキュリティー上、問題がある。

  これを見た時、初めてセッション管理とクッキー(cookie)に関連性がある事を知った。

  だが、ここでは深く考えず、軽く流す事にして、先に進む事にした。

  とりあえず、セッション管理を使ったPHP言語のプログラムを
触ってみた方が、理解が早いと思ったからだ。


  本を見ていく。
  個々の接続に関する値を保管するセッション変数を見てみる。

セッション変数について
PHP言語のセッション変数
セッション変数は、上のように配列の形をとっている。
キーで何の値を代入するのかを分類している。

  そして、以下の図のように、セッション変数は、セッションIDと紐づけされ
サーバーに保管されるというのだ。

セッションIDと紐づけされて保管されるセッション変数について
セッションIDと紐付けされているセッション変数
セッションIDと紐づけされてセッション変数はサーバーに保管される。
そのため、セッションIDは、個々の接続の際の情報を間違えなく
サーバーに保管する事ができる。

  これでセッションIDを発行すれば、個々の接続が特定できるため、
個々の接続に関する値をサーバーに保管する事ができる仕組みがわかった。


  だが「百聞は一見にしかず」なので、本を見ながら、
簡単なセション管理を使ったプログラムを作成してみる事にした。

  セッション管理を行うには、セッションを開始する関数が必要だ。

セッションの開始を宣言する関数

session_start();
セッション変数を使って、個々の値を覚えさせるには、
session_start();関数を使って、セッションを開始させる必要がある。

  そして以下のプログラムを作成した。

セッション管理を使った簡単なプログラム (test.php)
<?php

session_start();

if ( isset($_GET['name']) && !isset($_SESSION['name']) )
   {
   // GETで送られた「abc」の文字列をセッション変数に記録
   $_SESSION['name'] = $_GET['name'] ;
   }

?>

<HTML><HEAD><TITLE>テスト</TITLE></HEAD>
<BODY>

<?php
print  "セッションに送られた文字列: ".$_SESSION['name'] ;
?>

<HR>
<A HREF="test.php?name=abc">「abc」の文字列をサーバーへ送る</A><BR>
<A HREF="test.php">文字列を送らない</A>

</BODY></HTML>

  さて、上のプログラムを動かしてみる。

1回目の接続
プログラムで生成したセッションIDを表示
赤い線はセッションIDになる。
GET方式で「abc」の文字列をサーバーに送るリンクを張っている。

  そして「abc」の文字列を送ってみる。

「abc」の文字列を送ってみる
セッション変数に覚えさせた文字列を表示
サーバーに「abc」という文字列が記録され、
その記録された「abc」という文字が表示される。

これ以降、GETの細工をしていないリンクを押しても
サーバーに「abc」の文字列が記録されているため、
「abc」の文字列は表示され続ける。

  さて、サーバーに保管したセッション変数だが、初期設定では
/tmpディレクトリに保管される。

/tmp ディレクトリの様子
[root@server tmp]# ls -l
合計 ***
-rw-------    1 nobody   nobody         15  7月  8 11:06 sess_977dbf9370c7f272b631cdc92bee04f2
初期設定だと /tmp ディレクトリに保管される。

ファイル名は以下のようになる。
セッションIDが保管されたファイル名
さて、実際にファイルの中身を見てみる。
「sess_977dbf9370c7f272b631cdc92bee04f2」ファイルの中身
[root@server tmp]# more sess_977dbf9370c7f272b631cdc92bee04f2
name|s:3:"abc";
[root@server tmp]#
ファイルの中身を見てみると、クライアントから送った「abc」の文字列が
保管されている事がわかる。

  もちろん、セッション変数を保管させるディレクトリを変更する事は可能だ。


  だが、この時点では、サーバーが発行したセッションIDは、
どこで覚えさせるのか、全くわかっていなかった。

  でも、ここでは調べる事はしなかったが、その後、呆気ないぐらい簡単に
セッションIDを覚える仕組みがわかった。それについては後述しています。


  さて、セッションIDだが、同じ物を使うとセッションハイジャックの
危険性があると本に書いてあった。
  それを防ぐために、session_regenerate_id() 関数を使えば良いと
書いてあるのだが・・・

  セッションハイジャックって何やねん?

  だった (^^;;

  この時点では、セッションハイジャックという危険性がある事を
頭に入れただけで、何がどう危険なのか調べなかった。
  でも、後になって調べましたので、詳しい内容は後述しています。


買い物かごをどうするか さて、ここまで学んだ知識で、買い物カゴを提供するシステムを考える。 まずは以下のシステムを考えた。
最初に考えた買い物カゴのシステム
最初に考えた買い物かごの仕組み
通常のネット販売と同様、サーバーでプログラムを作成して
商品陳列と買い物カゴを同じプログラム上で動かす。
商品購入の情報は、データベースではなく、セッション変数に記憶させる。

  kazさんが考えておられるサイトとは形態が違うのだが、
まずは自分ができる範囲から取り組む事にした。

  そして、サンプルとして以下のプログラムを作成した。

  依頼者のkazさんからの要望で以下の要件を満たすプログラムだった。

kazさんからの要件
(1)
プログラムと画面部分を分離させる。
画面を作る人はHTMLさえ知っていれば、
商品陳列ができるようにするため
(2) 物を購入した時、購入明細を別のブラウザの画面に出す。

  (1)を満たすため、考えたのが、Smartyを使ったシステムだ。
  Smartyを使えば、プログラム部分と画面部分を分ける事ができる。

  ちなみに、Smarty導入についてですが、2006年に導入していますが、
時間がなくて、なかなか話が書けません (><)

最初に考えた買い物カゴのシステムのプログラム
プログラム部分(index.php)
<?php

 session_start();

 $userid = session_id();

 require_once("MySmarty.class.php");

 $my_smarty = new MySmarty();


 $my_smarty->assign("userid",$userid);
 $my_smarty->display("top.tpl");

?>

  そして画面部分だ。

最初に考えた買い物カゴのシステムのプログラム
(画面部分 top.tpl)
<HTML><HEAD><TITLE>テスト</TITLE></HEAD>
<BODY>

ネット販売のセッション管理テスト
<HR>

<A HREF="buy.php?userid={$userid}&shoid=1" target="_BLANK">商品A</A><BR><BR>
<A HREF="buy.php?userid={$userid}&shoid=2" target="_BLANK">商品B</A><BR><BR>
<A HREF="buy.php?userid={$userid}&shoid=3" target="_BLANK">商品C</A><BR><BR>

</BODY></HTML>

  そして要件(2)の、購入ボタンを押した時、購入明細を別のブラウザで出す時の
プログラムと画面部分。まずはプログラムから。

購入明細を別のブラウザで出す部分
プログラム部分(buy.php)
<?php

 session_start();

  if ( $_GET['shoid'] == 1 )
     {
     $_SESSION['shoid1'] += 1 ;
     }
  else if ( $_GET['shoid'] == 2 )
     {
     $_SESSION['shoid2'] += 1 ;
     }
  else
     {
     $_SESSION['shoid3'] += 1 ;
     }

 require_once("MySmarty.class.php");

 $my_smarty = new MySmarty();


 $my_smarty->assign("shoid1",$_SESSION['shoid1']);
 $my_smarty->assign("shoid2",$_SESSION['shoid2']);
 $my_smarty->assign("shoid3",$_SESSION['shoid3']);

 $my_smarty->display("buy.tpl");

?>

  そして画面部分。

購入明細を別のブラウザに出力させるプログラム
画面部分 (buy.tpl)
<HTML><HEAD><TITLE>テスト</TITLE></HEAD>
<BODY>

ご購入明細
<HR>

{if $shoid1 > 0 }
商品A:{$shoid1}個<BR><BR>
{/if}

{if $shoid2 > 0 }
商品B:{$shoid2}個<BR><BR>
{/if}

{if $shoid3 > 0 }
商品C:{$shoid3}個<BR><BR>
{/if}

</BODY></HTML>

  さて、プログラムの実行をしてみる。

商品購入の画面
商品購入のデモ画面

  それなりの画面になっている。
  そしてAの商品の購入のため、「商品A」の部分をクリックした。

購入明細画面
商品明細表示のデモ画面
一応、別のブラウザで明細が表示された

  そこで、商品陳列画面にしてみた。

今度は商品Bを購入してみる
他の商品の追加購入のデモ画面

  商品Bの部分をクリックしてみた。

購入明細画面
他の商品の追加購入後のデモ画面
最初に購入した商品A、次に購入した商品Bの明細が出ている

  問題点のあるプログラムだ。
  購入ボタンを押す際、「GET方式」でデータを送信するため、
HTMLソースを見たら、誰でもデータの送信方法などが丸見えだ。


  さて、このプログラムを持って、依頼者のkazさんと話をした。
  プロトタイプの開発だ。試作画面を作って、意思疎通をはかるのに最適だ。

  やはり最初にkazさんが望んでおられた物と、私ができる範囲で考えた物では
違う物なのだ。

kazさんが考えておられたシステム
kazさんの構想
Smartyを使って画面とプログラムを分離するのではなく、
最初から商品陳列の部分はHTMLのみの記述で、
買い物カゴは別のサーバーで運用する形だ。

  そこでkazさんと話をした。
  kazさんが望んでおられる形態に近い買い物カゴのサイトがあるという事で
そのサイトを紹介していただいた。

  そのサイトのHTML文を読んで、商品陳列部分と買い物カゴの部分が
それぞれ違うサーバーで運用している事がわかった。
  こんな仕掛けを作る必要があると思った。

こんな感じの買い物カゴのシステムを作る必要がある
実際に構築したいシステムの図
商品陳列の部分と、買い物カゴ(処理プログラム)を完全に分離。
商品陳列の部分は、各販売者が自力でHTMLで作成して、
買い物カゴ(処理プログラム)の部分を提供する形にする。

プログラム部分だが、kazさんからの紹介があったサイトは
Javaを使っていたが、Javaが組めない私の場合はPHPで作る。

  うーん、どうすれば上の図のような仕掛けができるのか。
  ふと思いついた。

  セッション管理を使えばできるやん!

  そこで以下のような仕掛けができそうだ。

こんな感じの買い物カゴのシステムができそうだ
セッション管理を使ってkazさんの依頼のシステムを描いた
商品陳列のサーバーには、HTMLで作成した商品陳列だけにする。
購入した商品を選ぶと、購入者は買い物カゴのサイトへ飛ぶだけでなく、
POST方式で購入品と購入数のデータを転送するようにしておく。

買い物カゴのサーバーは、購入者のセッションIDを発行して
購入者の特定ができるようにして、買い物カゴ自体は、
セッション変数で保管させる事にした。

  詳しく見ると以下のようになる。

  最初に購入者は商品陳列のページを見る。

購入者が最初に見る商品陳列のページ
購入者が最初に見る画面
購入者は商品陳列のページを見に行く。
このページは販売者が自由に商品の陳列ができるように
HTMLで作成可能にする。

ここではPHPやJavaなどのプログラムは一切使わず、
単なる商品陳列を行うだけにする。

欲しい商品を選んだ時、購入明細を見るため、買い物カゴのサイトへ
飛ぶようにしておくと同時に、POST方式で買い物カゴのサーバーへ
購入品と購入数のデータが飛ぶようにしておく。

  そして、購入者が買い物カゴのサイトに飛んだ時、以下の仕掛けが
働くようにしておく。

購入者が買い物カゴのサイトへ飛んだ時
買い物かごのサイトへ飛ばした時、セッション管理が働く
買い物カゴのサイトでは購入明細の表示だけでなく、
商品陳列のサイトから送られた購入品と購入数のデータを元に
購入明細の作成を行う。
これらのデータはセッション変数の中に格納される。

そして、最初の接続の場合、サーバーはセッションIDを発行して
購入者を特定できるようにしておく。

  さて、別の商品を購入したい場合は、再び、商品陳列画面に戻る。

再び商品を購入する場合
購入者が再度購入する場合
商品陳列の画面に戻って、別の商品を選ぶ。
そうすると、購入明細の閲覧と、購入品の処理のために
買い物カゴのサイトへ飛ぶ。

  2回目以降の買い物カゴへの接続なので、ブラウザから商品情報以外に
セッションIDも送信されるので、サーバーは購入者を特定できる。

2回目以降の買い物カゴへの接続
セッションIDを利用する仕組み
2回目以降の買い物カゴへの接続の場合、購入者(ブラウザ)は
1回目にサーバーから受け取ったセッションIDを送る事によって
買い物カゴのサイトは、購入者を特定する事ができる。

  さて、実際に、そんな事が実現できるのか確かめるため、
プログラムを作成してみる事にした。
  商品陳列と買い物カゴを完全分離して、しかも商品陳列部分を
販売者が自力でHTML言語で記述できるように、Smartyみたいな物は使わず
HTMLそのものだけで済むようにしておく事にした。

商品陳列部分(menu.html)
<HTML><HEAD><TITLE>テストII</TITLE></HEAD>
<BODY>
 
ネット販売のセッション管理テスト2
<HR>
<TABLE BORDER="2">

<FORM METHOD="POST" ACTION="menu.php">
<TR><TD>商品A</TD><TD>購入数
<SELECT name="kazu">
<option label="1" value="1" selected="selected">1</option>
<option label="2" value="2">2</option>
<option label="3" value="3">3</option>
<option label="4" value="4">4</option>
<option label="5" value="5">5</option>
</SELECT>個</TD>
<TD><INPUT TYPE="submit" VALUE='カゴに入れる'>
<INPUT TYPE=hidden NAME="shono" VALUE=10>
<INPUT TYPE=hidden NAME="gamen" VALUE=10>
</TD></TR>
</FORM>


<FORM METHOD="POST" ACTION="menu.php">
<TR><TD>商品B</TD><TD>購入数
<SELECT name="kazu">
<option label="1" value="1" selected="selected">1</option>
<option label="2" value="2">2</option>
<option label="3" value="3">3</option>
<option label="4" value="4">4</option>
<option label="5" value="5">5</option>
</SELECT>個</TD>
<TD><INPUT TYPE="submit" VALUE='カゴに入れる'>
<INPUT TYPE=hidden NAME="shono" VALUE=20>
<INPUT TYPE=hidden NAME="gamen" VALUE=10>
</TD></TR>
</FORM>


<FORM METHOD="POST" ACTION="menu.php">
<TR><TD>商品C</TD><TD>購入数
<SELECT name="kazu">
<option label="1" value="1" selected="selected">1</option>
<option label="2" value="2">2</option>
<option label="3" value="3">3</option>
<option label="4" value="4">4</option>
<option label="5" value="5">5</option>
</SELECT>個</TD>
<TD><INPUT TYPE="submit" VALUE='カゴに入れる'>
<INPUT TYPE=hidden NAME="shono" VALUE=30>
<INPUT TYPE=hidden NAME="gamen" VALUE=10>
</TD></TR>
</FORM>

</TABLE>

</BODY></HTML>

  まさにHTLMの世界。

  買い物カゴ(プログラム)部分は以下の通りだ。

買い物カゴ部分(menu.php)
<?php

if ( ! isset($_POST) )
   {
   die("この画面は直接は見れません");
   }

 session_start();

 $gamen = $_POST['gamen'] ; 
 $shono = $_POST['shono'] ; 
 $kazu = $_POST['kazu'] ;

if ( $gamen == 10 )
   {
   if ( $shono == 10 )
      {
      $_SESSION['shoid1'] += $kazu ;
      }
   else if ( $shono == 20 )
      {
      $_SESSION['shoid2'] += $kazu ;
      }
   else if ( $shono == 30 )
      {
      $_SESSION['shoid3'] += $kazu ;
      }
   }
else if ( $gamen == 20 )
   {
   if ( $shono == 10 )
      {
      $_SESSION['shoid1'] = 0 ;
      }
   else if ( $shono == 20 )
       {
      $_SESSION['shoid2'] = 0 ;
      }
   else if ( $shono == 30 )
      {
      $_SESSION['shoid3'] = 0 ;
      }
   }
?>

<HTML><HEAD><TITLE>テストII</TITLE></HEAD>
<BODY>
  
ネット販売のセッション管理テスト2
<HR>


<TABLE BORDER="2">

<?php if ( $_SESSION['shoid1'] > 0 ) { ?>
<TR><TD>商品A</TD><TD>購入数<?php print $_SESSION['shoid1'] ?></TD>

<TD><FORM METHOD="POST" ACTION="menu.php">
<INPUT TYPE="submit" VALUE='取り消す'>
<INPUT TYPE=hidden NAME="shono" VALUE=10>
<INPUT TYPE=hidden NAME="gamen" VALUE=20>
</FORM></TD></TR>
<?php } ?>

<?php if ( $_SESSION['shoid2'] > 0 ) { ?>
<TR><TD>商品B</TD><TD>購入数<?php print $_SESSION['shoid2'] ?></TD>

<TD><FORM METHOD="POST" ACTION="menu.php">
<INPUT TYPE="submit" VALUE='取り消す'>
<INPUT TYPE=hidden NAME="shono" VALUE=20>
<INPUT TYPE=hidden NAME="gamen" VALUE=20>
</FORM></TD></TR>
<?php } ?>

<?php if ( $_SESSION['shoid3'] > 0 ) { ?>
<TR><TD>商品C</TD><TD>購入数<?php print $_SESSION['shoid3'] ?></TD>
<TD><FORM METHOD="POST" ACTION="menu.php">
<INPUT TYPE="submit" VALUE='取り消す'>
<INPUT TYPE=hidden NAME="shono" VALUE=30>
<INPUT TYPE=hidden NAME="gamen" VALUE=20>
</FORM></TD></TR>
<?php } ?>

</TABLE>

<BR>
<A HREF="menu.html">購入画面に戻る</A><BR>

</BODY></HTML>

  これで試作プログラムは完成した。
  実際に動かしてみた。

プログラムの実行(商品陳列部分)
商品陳列の画面
商品Aを4個買う事にしてみた。
購入数を「4」を選択して「カゴに入れる」のボタンをクリックする。

  そして買い物カゴのサイトが開く。

プログラムの実行(買い物カゴ部分)
買い物かごに入れた様子
商品の購入明細が、商品Aを4個になっている。
正しく表示されている。

  商品の陳列と、購入した商品の処理を分離させる事ができた。
  この試作品をもって、kazさんの所へ尋ねた。
  kazさんが考えておられた感じの物だった。

  だが、実際の買い物カゴとなってくれば、セキュリティーの問題、
プログラムの問題があり、私がお助けできるのも、ここまでだった。

  あまりkazさんのお役には立てられなかったが、自分自身としては
勉強になった。
  機会を与えてくださったkazさんに感謝 m(--)m


セッション管理とクッキー(cookie)の関連

だが、ここで勉強を中断するわけにはいかない。 勤務先のネット販売システムの再構築だ。  ネット販売システム再構築の詳しい事は「システム奮闘記:その65」の オープンソースのECサイト:EC-CUBE導入をご覧下さい。 しかし、プログラムソースを読んだり、改造する事を考えたりすると セッションの話などはキチンと理解しておく必要がある。 PHP言語のセッション関数の使い方を、ひと通り覚えた所だったので、 今度は、クッキー(cookie)とは何ぞやという疑問を解決する事にした。 googleで調べて、あるサイトを見つけた。 (わかりやすい内容の所だったが、現在は、削除されていて閲覧できない) だが、私の場合、このシステム奮闘記を書くために、その時、学習した事を ノートに書いて、まとめているので、削除されても、原稿は書ける (^^)V Webに書かれていた内容は、以下のような内容だった。
クッキー(cookie)とは何か
Webに書かれていた内容を要約
ブラウザがサーバーから受信した内容をテキストファイルとして
クライアントマシンに保存しておき、交信の都度、
テキストファイル内の情報を、Webサーバーに送る事により
交信状態を保持する技術

(使用例として)
IDやパスワードをクッキー(cookie)で覚えさせる事で、2回目以降のログインを
何も入力しなくて済む便利な方法

  上の内容を図にすると、こんな感じになる。

クッキー(cookie)の仕組み・最初の接続
最初の接続時、サーバーからクッキーが送られる
サーバーから送られたクッキー(cookie)(テキストファイル)を
クライアントが受け取る。
クッキー(cookie)の仕組み(2回目以降の接続で)
2回目以降はクライアントが保管しているクッキーをサーバーへ送る形になる
そして、2回目以降の接続で、前回、サーバーから送られた
クッキー(cookie)(テキストファイル)をサーバーに送る仕掛けだ。

  より詳しく図にすると以下のようになる。

クッキー(cookie)の仕組み
クッキー(cookie)の仕組み
1回目の接続で、サーバーはクライアントに接続応答を返す際、
HTMLのデータと一緒に、ブラウザに覚えて内容を
クッキー(cookie)としてクライアントに送信する。

2回目以降の接続で、クライアントはサーバーへ接続要求を行う際に、
前回、サーバーから送られたクッキー(cookie)を一緒に送信する。
そして、サーバーは、接続応答の際に、ブラウザに覚えて欲しい内容を
HTMLのデータと一緒にクッキー(cookie)として送る。

  さて、どんな物か頭の中でわかっても、実際に、プログラムをして
自分の目で確かめないと気が済まないので、プログラムをしてみた。

  まずはクッキー(cookie)に必要な関数を紹介します。

PHP言語でのクッキー(cookie)の設定

setcookie("クッキー(cookie)名" , クッキー値 , 有効期限 );

有効期限の部分だが、有効期限の時刻(タイムスタンプ)を使う。
そのため、今から5分以内まで有効にしたい場合、
「time()+600」とする。time()は現在のUNIX時間で600は秒(5分)だ。

ここでは省略しているが、クッキーが有効なパスとURL、
SSLの時のみクッキーを送る指定ができる。

クライアントから送られたクッキーの受信

$_COOKIE["クッキー名"]

PHP4.1以降、クッキー値が格納される配列として
$_COOKIE[""] 配列が用意されている。
そのため、特に何もしなくても、クッキー値は受信できる。

この配列はグローバル変数だ。

  さて、この2点を押さえてプログラムを行った。

プログラム( cookie.php )
<?php

if ( isset($_COOKIE["xxx"]) )
   {
   $xxx = $_COOKIE["xxx"]."*" ;
   $yyy = 2 ;
   }
else
   {
   $xxx = "*" ;
   $yyy = 1 ;
   }

 setcookie("xxx",$xxx,time()+600);
?>

<HTML><HEAD><TITLE>cookie</TITLE>
<meta http-equiv="Pragma" content="no-cache">
</HEAD>
<BODY>

<?php

     if ( $yyy == 1 )
        {
        print "1回目<BR>";
        print  $xxx ;
        }
     else
        {
        print "2回目以降の接続<BR>";
        print $xxx ;
        }
?>
<HR>
接続回数を「*」の数で表しました。<BR>

<A HREF="cook.php">もう1回接続する</A>

</BODY></HTML>
接続がある度に「*」の表示数を増やす仕掛けにしている。
ブラウザにキャッシュが残らないようにする<meta>タグも付けておく。

  さて1回目の接続の時のブラウザの表示を見てみた。

1回目の接続の時
1回目の接続の結果
接続階数は「*」の数で表している。
1回目の接続は「*」の数が1個になっている。

そして2回目以降の接続を行うのは、矢印で指しているリンクを選ぶ

  2回目の接続の時の表示を見た。

2回目の接続の時
2回目の接続の結果
「*」の数が2個になっている。

1回目の接続の時に「*」が1個だと覚えさせた上、
2回目の接続の際、サーバーに「*」を1個送信し、
サーバーで「*」を1個増やした物を
クライアントに送り返している。

  どんどん接続回数を増やすと以下のように「*」の数も増える。

2回目以降の接続の時
2回目以降の接続の結果

  クッキー(cookie)の使い方がわかった。


  その後、telnetを使った実験を行うため、先程よりも簡単なプログラムを
作成してみた。

プログラム ( cookie.php )
<?php

if ( isset($_COOKIE["count"]) )
   {
   $times = $_COOKIE["count"] + 1 ;
   }
else
   {
   $times = 1 ;
   }

setcookie("count" , $times , time() + 600 );
?>

<html><head><title>cookie test</title><head>
<body>

<font size="5" color="red"><?php print $times ?></font>回目の接続

</body></html>

  まずはブラウザで表示結果を出してみる。

1回目の接続時のブラウザの表示
1回目の接続時は「1」と表示

  1回目の接続時、上の図のように「1」と表示された

更新ボタンを押してみる
更新ボタンを押してみる

  さて、「更新」ボタンにあたる物を押してみた。
  すると、接続回数の数字が加算された。

2回目の接続時のブラウザの表示
更新ボタンを押すと加算される

  どんなプログラムかを説明した所で、telnetを使った実験を行います。

  実は、クッキーの事を調べている間に、盗んだクッキーの悪用を知った。
  今回は、悪用の具体例が思いつかなかったので、悪用ではないが
telnetを使って、クッキー値(ここでは接続回数に当たる)を「3」として
サーバーに送信する実験を行ってみた。
  すると・・・

telnetでの接続&クッキー(cookie)の送信結果
[suga@server]$ telnet 192.168.X.Y http
Trying 192.168.X.Y...
Connected to 192.168.X.Y.
Escape character is '^]'.
GET /cookie.php HTTP/1.0
Cookie: count=3

HTTP/1.1 200 OK
Date: Wed, 04 Jul 2007 01:54:37 GMT
Server: Apache/1.3.37 (Unix) PHP/4.4.6
X-Powered-By: PHP/4.4.6
Set-Cookie: count=4; expires=Wed, 04 Jul 2007 02:04:44 GMT
Connection: close
Content-Type: text/html
 
 
<html><head><title>cookie test</title><head>
<body>
 
<font size="5" color="red">4</font>回目の接続
 
</body>Connection closed by foreign host.
[suga@server]$
上の赤い部分はサーバーに送信したクッキー値だ。「3」を送ってみた。
すると応答として数字の「4」(青い部分)が返ってきた。

  もし、クッキーでブラウザにIDとパスワードが記録されている場合、
クッキーの値が盗まれら、簡単にログインできる事を意味する。

  この話は、セッションハイジャックにもつながる話なのだが、
この時点では気がつかなかった。


セッションハイジャックの仕組み

ふと再び以下の本を取り出す。 「改訂新版 基礎PHP」(WINGSプロジェクト:インプレス) セッション管理について書かれているページを読む事にした。 クッキーとセッション管理の関連性について書かれていた。 この時、初めて・・・ セッション管理機能はクッキーそのものやん! という事が理解できた。 うーん、なんて遠回りした理解なんだと我ながら感心する (^^;; つまり、セッションIDは、クライアントに覚えさせるクッキー値なのだ。
セッションIDを食べるブラウザ
セッションIDをクッキーの原理を使って覚えさせる
PHP4以上のセッション管理の場合、クッキー(cookie)の仕組みを使って
セッションIDをブラウザに食べさせている。

  PHP4.1以上のセッション管理の仕組みを図にすると以下のようになる。

セッション管理の仕組み (PHP4.1以降)
セッション管理の仕組み
1回目の接続の際、クライアントが接続要求を行う。
サーバーはセッションIDを発行し、それをクッキー値として
クライアントへHTMLデータと共にセッションIDを送り、
ブラウザに覚えさせる。

そして2回目以降、クライアントが接続要求を行う際に、
前回の接続でサーバーから送られたセッションIDを送る。

サーバーは、クライアントから送られたセッションIDを見て
どの接続かを判断し、接続応答でHTMLデータをクライアントへ送る際に
再び、セッションIDをクッキー値としてブラウザに覚えさせる。

  知ってしまえば、簡単な仕掛けで非常に呆気ない。
  こんな事が理解できなかったため、ずーと足踏していたのか (--;;

  しかし、こんな事はよくあるので、めげずに前に進む。



  セッション管理がクッキーの技術を使った物だというのがわかった。

  セッションIDのクッキー名は初期状態だと「PHPSESSID」になる。

  ここでセッション管理を使った実験を思いついた。
  そこで簡単なプログラムを書いてみた。

プログラム (login.php)
<?php
 
 session_start();

 if ( isset($_POST['password']) )
    {
    //  認証画面で入力されたパスワードが正しい場合

    if ( $_POST['password'] == "test" )
       {
       // セッション変数にパスワードを記録する。
       $_SESSION['password'] = "test" ;
       }
    }

// 一度でも認証が成功した場合は、認証後の画面を出す。

if ( $_SESSION['password'] == "test" )
   {

$content = <<< EOD
<html><head><title>cookie test</title><head>
<body>
認証後の画面を出します
</body></html>
EOD;

   }
else // 認証前の画面を出す
   {
$content = <<< EOD
<html><head><title>cookie test</title><head>
<body>
パスワードを入力して下さい<br>
<form action="login.php" method="post">
<input type="text" name="password">
<input type="submit" value="送信">
</form>
</body></html>
EOD;

   }

print $content ;

?>

  早速、プログラムを実行してみた。

1回目の接続の画面
1回目の接続はセッションIDを覚えていないため、認証画面が出てくる
1回目の接続の際、サーバー側にパスワードを覚えさせていないため
パスワードの問い合わせを尋ねてくる。

  そしてパスワードを入力してみる。

パスワード入力
パスワードの入力

  「送信」ボタンを押すと以下の画面が出てくる。

認証後の画面
認証後の画面
この認証後の画面だが、一度、認証に成功すると 2回目以降の接続の場合、認証画面が出ずに、上の画面が出てくる。 これがセッション管理を使った2回目以降の接続で パスワードを入力せずにログインする仕組みだ。

  さて、セッションIDは初期状態では「PHPSESSID」という
クッキー名のクッキー値だ。

  ここで思いついたのが、認証に成功した場合のセッションIDを使って
サーバーに送れば、認証後の画面が出てくるかどうかの実験だ。

  早速、telnetを使った実験を行ってみた。

telnetを使った実験
suga@server]$ telnet 192.168.X.YY http
Trying 192.168.X.Y...
Connected to 192.168.X.Y.
Escape character is '^]'.
GET //login.php HTTP/1.0
Cookie: PHPSESSID=6d1e89fa42256f5872d570806a7cd71d
 
HTTP/1.1 200 OK
Date: Wed, 04 Jul 2007 02:43:44 GMT
Server: Apache/1.3.37 (Unix) PHP/4.4.6
X-Powered-By: PHP/4.4.6
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Connection: close
Content-Type: text/html
 
<html><head><title>cookie test</title><head>
<body>
認証後の画面を出します
</body></html>
Connection closed by foreign host.
[suga@server]$
赤い部分がクッキーの送信の部分だ。
青い部分がサーバーからの応答だ。
認証成功後の画面が出力されているのがわかる。

  上の実験で示した事は、認証を行わなくても、セッションIDがわかれば
ログイン後の画面が簡単に見れてしまう事を意味する。


  えらい恐いもんがあるなぁと思って、Webセキュリティーを調べたら、
上の実験のように、セッションIDを盗んで、セッション変数を取り出したり、
ログインしたりする事を「セッションハイジャック」と呼ばれる。

セッションハイジャックの仕組み
もし悪人がセッションIDを盗聴した場合
上の図のように、悪い奴は、セッションIDを盗聴したり
何らかの形で盗む出す。
不正取得したセッションIDを利用して情報を不正入手
そして盗み出したセッションIDを使って、不正に情報を取り出したり
不正に操作を行ったりする。

  管理者でログインしている際に、セッションIDが盗まれ、それを使って
サイトを乗っ取る事も考えられる。

  わかった風に、こんな事を書いているのだが・・・

  この編集をしている間に初めて危険性を認識した (^^;;

  のだった。

  セッションハイジャックを防ぐ方法として、クライアントとサーバーの間は
SSL通信(通信データの暗号化)を行う方法がある。

セッションハイジャックを防ぐ対策
セッションハイジャックを防ぐ方法。SSLでデータの暗号化
通信データの中身を暗号化する事によって、悪い奴が盗聴したくても
セッションIDを解読できないようにする方法がある。

データの暗号化については「システム奮闘記:その64」
(SSLを使ったWebデータの暗号化と認証局の話)をご覧下さい。

  それ以外にも方法がある。

  1回きりのセッションIDを使う事だ。

セッションIDを変更する関数
session_regenerate_id();
PHP4.3.2以降から実装された関数だ。

  さて、session_regenerate_id()関数を使って、セッションIDが
変更されるかどうかを確認するプログラムを作成してみた。

プログラム(chgid.php)
<?php
  
 session_start();

 // 古いセッションID
 $oldid = session_id();

 // セッションIDを新しい物に変更する
 session_regenerate_id();
 $newid = session_id();

?>

<html><head><title>セッションIDの変更</title></head>
<body>

古いセッションID : <?php print $oldid ?> <br>
新しいセッションID : <?php print $newid ?>

</body></html>
古いセッションIDと、新しいセッションIDの両方を出力する
プログラムだ。

  さて、実行してみた。

プログラムの実行結果
1回キリのセッションIDの発行実験の結果
セッションIDが変更されているのが、わかる。

PHP5.1より低いバージョンだと、これでも不十分だという。
それについては、別の機会に触れたいと思います。

  接続を行う度に、セッションIDを変更しておけば、例え、セッションIDが
盗聴されたとしても、盗聴される危険性が減る。

  ただし、セッションIDを変更する前に、セッションIDが盗まれた場合、
悪用されてしまうため、session_regenerate_id()関数だけに頼るのは
良いとは言えないのだ。


SSLの暗号化通信以外はクッキー(cookie)を使えなくする方法

セッションIDの漏洩を防ぐ方法の1つとして、SSLを使った 暗号化通信の話に触れました。 セッションID以外でもクッキーの情報で、物によっては、途中の経路で 盗聴されてはマズい物がある。 そこでSSL通信以外ではクッキーが使えない設定について触れたいと思います。
PHP言語でのクッキーの設定

setcookie("クッキー名" , クッキー値 , 有効期限 , 有効パス , 有効ドメイン , SSL通信の有無);

赤い部分は該当のクッキー名が安全なSSL通信の条件下でしか
クッキーのやりとりが行えないための設定だ。
SSL通信のみにする場合、「1」にする。

SSL通信以外の普通の通信でも使えるようにするには「0」にするか、
この引数は省略すれば良い。

もちろん、初期設定の状態では、SSL通信以外でも通信は可能になっている。

  実際に、上の設定で「1」(SSL通信のみ)にした場合、
クッキーのやりとりがどうなるのか見てみる事にした。


  そこで、ブラウザの更新ボタンを押せば、接続回数が加算されるプログラムを
作成して、実行させてみる事にする。
  まずは、SSL通信以外でも使えるプログラムから。

プログラム
<?php

$count = $_COOKIE['count'] ;

if ( !isset($count) )
   {
   $count = 1 ;
   }
else
   {
   $count += 1 ;
   }

setcookie("count",$count , time()+600 ,'' ,'' , 0);

?>

<html><head><title>SSL限定の通信実験</title></head>
<body>
SSLを有効にしていない場合<br><br>

接続回数: <font size="5"><?php echo $count ?></font>

</body></html>
1回目のプログラム実行時は「接続回数:1」と表示される。
更新ボタンを押せば「接続回数」の数字は増加されていく。

  さて、本当にプログラムの実行回数分、接続回数の数になるのか確かめるため
プログラムを実行してみた。

SSL暗号化通信以外でもクッキーを有効にした場合の
プログラムを実行
プログラムを実行
更新ボタンを押す
更新ボタンを押してみた結果
さらに更新ボタンを押す
さらに更新ボタンを押してみた結果

  キチンと接続回数の数が増えている事がわかる。

  では、次のようにプログラムを変更してみる。

プログラム
<?php

$count = $_COOKIE['count'] ;

if ( !isset($count) )
   {
   $count = 1 ;
   }
else
   {
   $count += 1 ;
   }

setcookie("count",$count , time()+600 ,'' ,'' , 1);

?>

<html><head><title>SSL限定の通信実験</title></head>
<body>
SSLを有効の場合<br><br>

接続回数: <font size="5"><?php echo $count ?></font>

</body></html>
1回目のプログラム実行時は「接続回数:1」と表示される。
更新ボタンを押せば「接続回数」の数字は増加されていく。

ただし、通信方式は、SSL暗号化通信を行っている時だけだ。

  さて、プログラムを実行するのだが、SSL通信以外では
クッキーがうまく働かない事を示すために、URLの部分は「https」ではなく
「http」で接続してみた。
  つまり非SSL通信なのだ。

プログラムを実行
非SSL通信の場合
1回目のプログラム実行の時「1」と表示されるが、
その後、何度も更新ボタンを押しても、加算されずに「1」のままだ。
うまくクッキーが働いていない事を示している。

  さて、ふとブラウザにクッキー名が登録されているか見てみた。
  するとブラウザにはクッキーの登録があった。

ブラウザのクッキーの登録を見る (Firefox2.0.0.6)
ブラウザのクッキー登録の状態
ブラウザにはクッキーが登録されている。
しかし、SSL通信のみ、サーバーにクッキーを送る設定になっている。

  非SSL通信の場合、クッキーが無効になるので、てっきり私は
ブラウザがサーバーに接続した際に、サーバーがクッキー名とクッキー値を
送信しないと思っていたが、そうではないので、意外だった (^^;;

  さて、これを図にすると、以下のようになる。

図にすると
2回目以降の接続でクライアントから送られれるクッキーを受け取らない仕組み
最初にプログラムを実行した時、サーバーからクッキーが送られ
ブラウザはクッキーを受け取る。

しかし、2回目以降の接続の際、ブラウザは非SSL通信の場合、
クッキーをサーバーに送信しない。
そのため、1回目の接続と同じ状態になる。

  今度は、URLの部分は「https」で接続してみた。SSL通信だ。

プログラムを実行
SSL通信で行ってみる
更新ボタンを押す
SSL通信で更新ボタンを押してみる
クッキーのやりとりができるため、プログラムが正常に動いた。
その後、更新ボタンを連打すると、どんどん加算されていくのが確認できた。

  安全性を確保するための仕掛けがあるのだなぁと思った。

  SSLの設定の話ですが、ここでは割愛します。
  だって、SSLの話まで書いたら、編集しきれないもーん (^^)
 SSLについて、詳しくは「システム奮闘記:その64」の
SSLによる暗号化通信と認証局・電子署名をご覧ください


安全性確保のためセッション情報を消去する仕組み さて、話はセッション管理に戻します。 ところで、セッションIDを応用した認証でログインして、 その後、ログオフ(ログアウト)した場合、使わなくなったセッションIDは 破棄した方が安全だ。 使い終えたセッションIDを生かしていると、それを悪用される可能性が 考えられるからだ。 セッションIDを破棄する関数がある。session_destroy()関数だ。 全てのセッションに関する情報を消してくれるという。 全てのセッション情報って何やろ? という事で、本やサイトなどを調べるが、これだと思う情報が見つからない。 ところで本を見ると、session_destroy()関数を使っても セッション変数は消去する事はできないという。 本当かどうか確かめるため、次のプログラムを動かしてみる事にした。
プログラム
<?php
  
 session_start();

 // セッションID
 $id = session_id();

 // セッション変数に代入
 $_SESSION['test'] = "Test data !!" ;

 // セッションの破棄
 session_destroy();

?>

<html><head><title>セッションの破棄実験</title></head>
<body>

破棄したセッションID : <?php print $id ?>

</body></html>
セッションを起動した後、セッションIDを取り出し、
セッション変数に値を代入した後、セッションを破棄させる。

本やサイトの情報だと、プログラム実行して、
セッション変数に値を代入した後、セッション破棄しても、
ハードディスク内には、それらのデータが残るというのだ。

  さて、上のプログラムの実行してみた。

プログラムの実行結果
破棄したセッションIDを表示

  さて、セッション変数の中身が生き残っているかどうか、
セッション変数を記録しているディレクトリを見てみるが・・・

「ls」でリストをセッション変数が残っているかどうか確認してみた
[root@server tmp]# ls 
sess_055c040db2defd34ede24112819113ae  sess_5bf8a0f39727778fbe33e50d93e130c2
sess_b08faa535310d9e23a5f545c19fc34b6  sess_077137e323322f57ad3daef019dab92a  
sess_5f112f423b9a4eb066e5f0cb372d63ea  sess_b26d2c57da71401d549bdf926a7310ab
sess_0e46e3231c534429a4e757ded1208a95  sess_61ad03b67fa3fc3a12d3b96f4051912c
(以下省略)

  さて、問題のセッション変数を格納しているファイルを探すのだが・・・

  全くあらへんやん!

  セッション変数の内容を記憶させているファイル
  「sess_df75ceaa1648e2d5f78b08a8f28158a3」が見つからないのだ。

  セッションを破棄した事で、セッション変数の内容を記録させている
ファイルは消去される事がわかる。

  でも、思った。
  サイトに書かれているセッションを破棄する session_destroy()関数は
セッション変数を消去できないと書いている。

  サイトなどに書かれているような感じで、次のプログラムを作成した。

プログラム
<?php
  
 session_start();

 // セッションID
 $id = session_id();

 // セッション変数に代入
 $_SESSION['test'] = "Test data !!" ;

 // セッションの破棄
 session_destroy();

 // セッションを破棄した後のセッション変数を取り出す。
 $ses = $_SESSION['test'] ;

?>

<html><head><title>セッションの破棄実験</title></head>
<body>

破棄したセッションID : <?php print $id ?>
破棄した後でも生き残っているセッション変数 : <?php print $ses ?>

</body></html>
セッションを破棄した後で、そのセッションのセッション変数を
取り出してみる事にした。(ピンク部分)
そして取り出したセッション変数が表示してみる。(青い部分)

  早速、上のプログラムを実行してみる事にした。
  すると・・・

プログラムの実行結果
セッションを破棄してもセッション変数は生きていた
セッションを破棄した後でも、プログラム上ではセッション変数は生きている。

どうやら、セッションを破棄すれば、セッション変数を記録するファイルは
消去されるのだが、プログラムが実行終了するまでは、
メモリ上に、セッション変数が保管されたままになる。

だが、ふと疑問に思った。
メモリ上に残っているだけなら、別にセッション変数を初期化する
必要もないではないか。

プログラムを実行させている間、余分や変数があると
メモリを食うから対策が必要というなら、理解できる。
プログラム実行中に、セッションを破棄した後に、新しいセッションを
起動させるなら、セッション変数の初期化が必要なのはわかる。
でも、そうでない限り、変数の衝突は考えずらいため
初期化の必要な理由が見えてこない。

でも、セッションを破棄する前に、セッション変数を初期化する事が
常套手段になっている事から、なぜかはわからないが、
初期化するのが良いみたいだ。

  うーん、釈然としない物があるが、わかり次第、載せたいと思う。


  ところで、不要になったセッション変数は、綺麗さっぱりと
消去する方法だが、簡単な方法でできる。

セッション変数を綺麗さっぱり消去する方法

$_SESSION[] = array();


  セッション変数は配列なので、配列の初期化を行うのと同じようにすれば良い。
  これをセッションを破棄する session_destroy() 関数の実行前に
行えば良いのだ。


  もう1つ、ログオフ(ログアオウト)するための処理として
ブラウザに覚えさせたセッションIDを消去する必要がある。
  セッションIDはクッキー値なので、クッキー値を消去する方法を使えば良い。

ブラウザに覚えさせたセッションIDを消去する

setcookie(session_name(), '', time()-42000, '/');

session_name()関数は、セッション名を取り出す関数。
初期状態のセッション名は「PHPSESSID」だが、
設定上、セッション名を変更する場合があるため、
この関数を使うのが無難。

有効期限だが、現在のUNIX時間から42000秒を引いた値になっている。
なぜ、この値になっているのかは謎だが、この値を使っているプログラムを
結構、見かける。だが、別に42000秒でなくても、10秒でも100秒でも良い。
要するに、有効期限を、過去のUNIX時間にしておけば、
この関数を使った時点で、有効期限が過ぎているので、
自動的にブラウザに対して、クッキー名とクッキー値
(ここではセッション名とセッションID)を忘れてくださいを意味している。

  これでブラウザに覚えさせたセッションIDや付随している情報を消去すれば
不要になったセッションに関する情報は、綺麗に消えてなくなる。

  安全性を高めるためにも綺麗にするのが良いというわけだ (^^)

php.iniにおけるセッション管理に関する設定 今までの説明では、セッション管理の設定は、初期設定の値の状態で 書いていました。 編集するのが大変なので、初期設定のまま、編集を終わらせようと考えた。 だが、設定ファイルを触っていると・・・ 欲が出てもうた (^^;; という訳で、できるだけ載せようと考えた。 そこで、PHPのセッション管理部分の設定についての話に触れたいと思います。 PHP言語の設定ファイル「php.ini」の記述に、セッション管理の部分がある。 ここではPHP-4.4.7のバージョンでの話で書きましたが、PHP5でも 今から説明する部分は差はありません。 セッション変数が格納されるディレクトリの設定から
セッション変数が格納されるディレクトリの設定

; Argument passed to save_handler.  In the case of files, this is the path
; where data files are stored. Note: Windows users have to change this 
; variable in order to use PHP's session functions.
; As of PHP 4.0.1, you can define the path as:
;     session.save_path = "N;/path"
; where N is an integer.  Instead of storing all the session files in 
; /path, what this will do is use subdirectories N-levels deep, and 
; store the session data in those directories.  This is useful if you 
; or your OS have problems with lots of files in one directory, and is 
; a more efficient layout for servers that handle lots of sessions.
; NOTE 1: PHP will not create this directory structure automatically.
;         You can use the script in the ext/session dir for that purpose.
; NOTE 2: See the section on garbage collection below if you choose to
;         use subdirectories for session storage
;session.save_path = /tmp

ピンクの部分が設定箇所になる。今は「;」で無効になっている。
初期設定では「/tmp」のディレクトリーに格納される。

  次に、セッションIDをクライアント側に覚えさせる手段として、
クッキーを使う設定だ。

セッションIDをクライアント側に覚えさせる設定
(php.ini)

; Whether to use cookies.
session.use_cookies = 1

初期設定では「1」だ。
「1」はクライアント側がセッションIDを覚える方法として
クッキーを使う設定だ。

「0」に設定すると、クッキーが使われない。

  実際に「session.use_cookies」の値を「0」にすれば、
クライアント側がセッションIDを覚えるのに、クッキーを使わなくなるのか
確かめる事にした。

セッションIDをクライアント側に覚えさせない設定に変更 (php.ini)

; Whether to use cookies.
session.use_cookies = 0

「0」に設定すると、クッキーが使われない。
実際にどうなるのか自分の目で確かめてみる。

  そこで以下のプログラムを作成した。

プログラム
<?php

session_start();

$id = session_id();

?>

<html><head><title>クッキーの実験</title></head>
<body>

<?php

echo $id ;

?>

</body></html>
サーバー側が発行したセッションIDを表示するプログラムだ。
このセッションに関する情報がブラウザに保管されるかどうかを
確認してみる事にした。

  プログラムの実行前に、クライアント側のクッキーの情報を全て消去した。

クッキーの情報を消去した (Firefox2.0.0.6)
クッキー(cookie)情報を消去した

  早速、プログラムを実行してみた。

プログラムの実行結果
新規にセッションIDが発行されている

  サーバーから発行されたセッションIDが表示される。
  「更新」ボタンを押してみる。

プログラムの再実行結果
別のセッションIDが発行されている
先ほどとは違うセッションIDが表示された。

  クッキーを使った場合だと、サーバーから発行されたセッションIDを
再実行の時、クライアントが送るため、同じセッションIDが表示されるが、
クライアント側はセッションIDを持っていないため、サーバー側では
新たにセッションIDを発行している。

  そして、クライアント側のクッキー情報を見てみる事にした。

ブラウザのクッキーの情報 (Firefox2.0.0.6)
クッキーの設定状態

  クッキーの情報は、プログラム実行前に削除した状態と同じだ。
  すなわり、クッキーを使わない設定が動いている事が確認できた。

  やっぱり自分の目で確認してみる必要がある (^^)


セッション名の設定箇所

次に、クライアントのセッションIDを送る場合、セッション名(クッキー名)の 指定を行う設定だ。
セッション名の設定( php.ini )

; Name of the session (used as cookie name).
session.name = PHPSESSID

セッションはクッキーの技術を応用したものだ。
初期設定ではセッション名(クッキー名)は「PHPSESSID」だ。
この部分を変更すれば、セッション名は変更できる。
ただし、セッション名はアルファベッドの綴りのみです。

  実際に、セッション名(クッキー名)が「PHPSESSID」になっているか
ブラウザを確認してみる事にした。

ブラウザのクッキーに関する情報 (Firefox2.0.0.6)
セッション名がPHPSESSIDになっている様子
セッション名(クッキー名)が設定通り「PHPSESSID」になってる。
ある本には、セキュリティー向上のため、セッション名を
別の名前にする方法が書いてあった。
どれくらい有効なのかは、私は専門家でないため、何とも言えないが (^^;;


クライアントのセッションIDの受け取り許可の設定 次に、ブラウザから送られたセッションIDの検出は、 クッキーのみから行う設定を見てみる。
セッションIDの検出はクッキーに限定するかどうかの設定
(php.ini)

; This option enables administrators to make their users invulnerable to
; attacks which involve passing session ids in URLs; defaults to 0.
; session.use_only_cookies = 1

設定が「1」だと、クッキーのみから検出を行う設定で、
初期設定は「0」になっている。

「0」にすると、GET(URLの後ろにセッションIDを埋め込む方式)で
セッションIDを検出する事が可能になる。
しかし、セッションIDを盗聴した奴が、URLの後ろに
セッションIDを埋め込んで、悪用する危険がある。
悪用防止のための機能で、PHP4.3以降につけられた。

  では、実際にセッションIDの検出をクッキーのみになるのかどうか
確かめる事を考えた。

  そこで以下の実験を行う事にした。

比較実験
(1) クッキー以外のでセッションIDの検出可能な状態で
ブラウザからGET方式でセッションIDを送信してみる
(2) クッキー以外のでセッションIDの検出不可能な状態で
ブラウザからGET方式でセッションIDを送信してみる

  上の実験を正しく行うため、ブラウザがサーバーへクッキーを
送信しないようにする必要がある。
  そこで、サーバーからクッキーが送られても、ブラウザで受け取らないような
設定を行う。

ブラウザでクッキーの受け取りをしない設定 (Firefox2.0.0.6)
ブラウザのクッキー受け取り拒否の設定
クッキーを無効にする事により、クッキーとして覚えさせた
セッションIDをサーバーに送れないようにする。

  次のプログラムを作ってみる。

プログラムを実行してみる
<?php

session_start();

$id = session_id();

// 1回目の接続の場合、セッション変数は「1」を覚えさせる
// 2回目以降の接続は、接続の度に加算される。
// つまり接続回数を表示させている。

if ( !isset($_SESSION['count']) )
   {
   $_SESSION['count'] = 1 ;
   }
else
   {
   $_SESSION['count'] += 1 ;
   }

?>

<html><head><title>クッキーの実験</title></head>
<body>

<?php

echo $id ;
echo "<br>" ;

echo $_SESSION['count'] ;

?>

</body></html>

  比較実験に入る前に、ブラウザがクッキーを受け取らない設定の場合、
クッキーが送信できていない事を示す実験を行った。

  プログラムを実行してみた。

プログラムを実行
クッキー拒否の設定で実行
セッションIDは「d4e4129560ce78a56dc1412bdec242aa」で
セッション変数は「1」だ。

  ブラウザはクッキーを受け取らない設定になっている。
  なので「更新」を押しても、クッキーはサーバーに送られないため
サーバーからは新たにセッションIDが割り当てられる上、
セッション変数は「1」のままだ。

  さて、実際に「更新」ボタンを押してみる。

「更新」ボタンを押してみる
クッキー拒否の設定で再度実行
サーバーから新たに「1163ba856b9ff4ba69006fbaf9b1e42b」の
セッションIDが割り当てられた。
セッション変数は「1」のままだ。

  やはりサーバーから送られるクッキーを受信できないため、
サーバーへ接続する度に、サーバーからセッションIDが新たに割り当てられる上、
セッション変数も初期値の「1」のままだ。

  これでブラウザがサーバーからクッキーを受信できていない事を示して
比較の実験を行う事にした。

■■■ 実験(1) ■■■

  さて、ブラウザがクッキーを受け取らない設定でも、
URLの後ろにセッション名(PHPSESSID)とセッション値(セッションID)を付けて
サーバーに送った場合は、どうなるのか、確かめる事にした。
  つまり、GET方式でサーバーにセッションIDを送るのだ。

  そこで先ほどと同じプログラムを実行してみる。

プログラムを実行
クッキー拒否の実験1
セッションIDは「3abbd14352381d83b0ef37e424451516」が
割り当てられた。

  さて、URLの後ろにセッションIDとセッション名を付けてみる。

URLの後ろにセッションIDとセッション名を付けてみる
URLに付加してみる
URLの後ろにセッション名とセッションIDを記述して
GET方式でサーバーにセッションIDを送る準備をする。

  URLの後ろにセッションIDとセッション名を付けて、
プログラムを実行してみた。

URLの後ろにセッションIDとセッション名を付けてプログラムを実行
GET方式ではサーバーはクッキーを受け取る
GET方式でサーバーに送ったセッションIDは、サーバーが受け取り
サーバーは処理を行った。
そのためセッションIDには変更がない上、セッション変数は
プログラム通り、初期値から「1」を加算した「2」の値になっている。

  この段階ではセッションIDの検出は、クッキー以外での行っている事を
示している。

■■■ 実験(2) ■■■

  さて、次は、ブラウザから送信されるセッションIDは
サーバーではクッキー以外では受け取らない設定にしてみた。

セッションIDの検出はクッキーのみから行う
(php.ini)

; This option enables administrators to make their users invulnerable to
; attacks which involve passing session ids in URLs; defaults to 0.
session.use_only_cookies = 1

セッションIDの検出をクッキーのみに限定する設定にした。
これでGETやPOSTでセッションIDを送っても、反応しないはず。

  もちろん、この時、ブラウザはクッキーでセッションIDを送信しないように
ブラウザは、サーバーから送られるクッキー値を記憶しない設定にしている。

サーバーが発行したセッションID
サーバーが発行したセッションID
セッションIDは「7bb71803338e309a2c7e3c8f0b31c126」で
セッション変数は「1」だ。

  さて上のブラウザの状態で、URLの後ろにセッションIDを付けて
サーバーに送ってみる。

URLの後ろにセッション名「PHPSESSID」とセッションIDを付けて送り込む
URLに付加してサーバーに送ってみる
表示されたセッションIDを使って、URLの後ろにセッション名と
セッションIDを送信してみる。

  さて、実験結果は・・・

実験結果
サーバーでは送られたクッキーの受け取りを拒否
ブラウザからGET方式(URLの後ろにセッションIDを付ける)で送信された
セッションIDは、サーバーでは検出しないため、サーバーは
新たにセッションIDを割り当てている。

セッション変数も初期値の「1」が表示されている。

  実験成功 (^^)V

  この設定をしておけば、悪い奴がURLの後ろにセッションIDを埋め込むという
セッションハイジャックを防ぐ事ができる。

  クッキーが使えないブラウザ(一部の携帯)向けのサイトを除いて
セキュリティーを向上させるために必要な設定になっている。


クッキー(cookie)が使えないブラウザの場合 ところで、クッキーが使えない一部のブラウザのため、 セッションIDをHTMLに埋め込む設定がある。
セッションIDをHTMLに埋め込む設定

; trans sid support is disabled by default.
; Use of trans sid may risk your users security. 
; Use this option with caution.
; - User may send URL contains active session ID
;   to other person via. email/irc/etc.
; - URL that contains active session ID may be stored
;   in publically accessible computer.
; - User may access your site with the same session ID
;   always using URL stored in browser's history or bookmarks.
session.use_trans_sid = 0

初期設定では「0」(無効)になっている。
「1」にすると、HTMLにセッションIDを埋め込む設定が有効になる。

  この設定を行えば、<a href="xxx">のようにリンクのタグには
URLの後ろにセッションIDが付加される。GET方式だ。

  <form>のタグを使った場合、<input hidden>を使った
PUT方式でセッションIDを送信できるようにHTMLに追加される。


  ここでも論より証拠。
  早速、以下のプログラムを作成してみた。

プログラム
<?php

session_start();

$id = session_id();

?>

<html><head><title>セッションIDを付加の実験</title></head>
<body>

セッションID : <?php echo $id ?> <br><br>

&lt;a&gt;のリンクタグ<br>
<a href="sid.php">リンク</a>

<br><br>


&lt;form&gt;のタグ<br>

<form action="sid.php" method="post">
<input type="submit" value="押す">
</form>

</body></html>
<a>タグと<form>タグでリンクを張ったHTML文。
実験では、この部分に変化があるかを確かめる事にした。

  さて、最初は「session.use_trans_sid = 0」の設定にして
プログラムを実行させてみる。
  以下の画面が表示される。

表示画面
クッキーをHTMLに埋め込んだ場合の画面
ブラウザがサーバから受信したHTML文
<html><head><title>セッションIDを付加の実験</title></head>
<body>

セッションID : d729974dabcbff05835f94b4a6dfdc65 <br><br>

&lt;a&gt;のリンクタグ<br>
<a href="sid.php">リンク</a>

<br><br>


&lt;form&gt;のタグ<br>

<form action="sid.php" method="post">
<input type="submit" value="押す">
</form>

</body></html>

  予想通りのHTML文がサーバーからブラウザに送られる。

  だが、「session.use_trans_sid = 1」の設定にして
HTML文に変化があるかなぁとワクワクしながら、プログラムを実行させてみる。

表示画面
結果に変化がなかった
ブラウザがサーバから受信したHTML文
<html><head><title>セッションIDを付加の実験</title></head>
<body>

セッションID : d729974dabcbff05835f94b4a6dfdc65 <br><br>

&lt;a&gt;のリンクタグ<br>
<a href="sid.php">リンク</a>

<br><br>


&lt;form&gt;のタグ<br>

<form action="sid.php" method="post">
<input type="submit" value="押す">
</form>

</body></html>

  サーバーから受信したHTML文を見てみるが・・・

  さっきと全く同じやん (--;;

  うーん、本の説明のような結果にならない。

  そこで考えた。
  今は、ブラウザがサーバーから送信されるクッキーを受信できる状態だ。

  なのでクッキーが使えない携帯と同じように、ブラウザでも、
サーバーから送信されるクッキーを拒否する設定にすれば良いと思った。

  早速、ブラウザはサーバーから送信されるクッキーを拒否する設定を行った。

ブラウザでクッキーの受信を拒否する設定 (Firefox2.0.0.6)
ブラウザ側でクッキーの受信拒否

  これでクッキーが受信できないブラウザになった。
  そしてプログラムを実行してみた。

表示画面
ブラウザに出してみる
画面は見た目は全く同じだ。

  さて、サーバーからブラウザに送られるHTML文を見てみる事にした。
  すると・・・

サーバーから送られたHTML文
<html><head><title>セッションIDを付加の実験</title></head>
<body>

セッションID : d729974dabcbff05835f94b4a6dfdc65 <br><br>

&lt;a&gt;のリンクタグ<br>
<a href="sid.php?PHPSESSID=d729974dabcbff05835f94b4a6dfdc65">リンク</a>

<br><br>


&lt;form&gt;のタグ<br>

<form action="sid.php" method="post">
<input type="hidden" name="PHPSESSID" value="d729974dabcbff05835f94b4a6dfdc65" />
<input type="submit" value="押す">
</form>

</body></html>
赤い部分と青い部分が、追加されたHTML文の部分だ。
クッキーが使えないブラウザのため、GET方式、POST方式で
セッションIDをサーバーに送れるようにHTML文が追加されているのだ。

  さて、この実験を通じて以下の事がわかった。
  「session.use_trans_sid」の設定を「1」にしても、クッキーが受信可能な
ブラウザの場合は、サーバーはHTML文に何も細工しない。
  しかし、クッキーが受信不可能なブラウザの場合は、クッキー以外でも
セッションIDをサーバーに送信できるように、URLの後ろに付加するGET方式や
<input hidden>を使ってPOST方式で送信するようにHTML文を細工する。


  ただ、この設定は、むやみに使う事はお勧めできないようだ。
  ブラウザ側の設定で、クッキー受信拒否を行えば、簡単にセッションIDを
知る事ができるのだ。HTML文を見れば良いからだ。
  セッションIDが丸見えなので、セキュリティー上、良いとは言えない。

セッション管理の自動起動

次は、PHPのプログラムを実行させた時、セッション管理機能を 自動的に起動させるかどうかの設定を見てみる。
セッション管理機能の自動起動(php.ini)

; Initialize session on request startup.
session.auto_start = 0

初期設定では「0」になっている。
もし、「1」にすれば、セッション開始を自動的に行うため、
セッション開始のsession_start()関数が不要になる。

しかし、何でもかんでも、phpのプログラムを実行させた時に
セッション開始するのは、セキュリティー上、良くないため
「0」にするのが無難のようだ。


セッションIDとセッション変数の寿命について 次にセッションIDやセッション変数の寿命について見てみる。
セッションの寿命の設定 (php.ini)

; Lifetime in seconds of cookie or, if 0, until browser is restarted.
session.cookie_lifetime = 0


  この設定を見て、ふと思った。
  セッションの寿命に関する設定は、他にもある。

  「session.gc_maxlifetime」の設定だ。

この2つの設定の違うは何? (php.ini)
session.cookie_lifetime = 0
session.gc_maxlifetime = 1440

  どっちもセッションの寿命に関する設定だ。
  設定ファイルの説明を見たりするが・・・

  わからぬのらー!!

  だった (^^;;

  その混乱の元は以下の理由からだった。

混乱の原因は、こんな思い込みからだった
session.gc_maxlifetime = 1440
この設定は、ブラウザが覚えるセッションIDの寿命だと
思い込んでいた。

なので「session.cookie_lifetime」の設定部分で
クッキーの寿命と書いているため混乱してしまった。

  あーでもない、こーでもないという感じで調べていった。
  すると、勘違いである事がわかった。

「session.gc_maxlifetime」の設定の意味は (php.ini)

; After this number of seconds, stored data will be seen as 'garbage' and
; cleaned up by the garbage collection process.

session.gc_maxlifetime = 1440

調べてみると、この設定はセッションデータ($_SESSION変数)の
有効期限、すなわち寿命の設定だった。

よく考えれば、設定ファイルの説明を読めば良かったのだが、
その時は、本等で得た知識で思い込んでいたため、
設定ファイルの説明を読まなかった。

設定ファイルの説明を読むと、秒を設定する事で、
保管されていたデータを「ゴミ(garbage)」と見なし、
ゴミ収集のプロセスによって処理される事が書いている。

「session.gc_maxlifetime」の「gc」は
「garbage  collection」(ゴミ収集)の意味だ。

  ここで合点がいった (^^)
  整理すると以下の通りだ。

この2つの設定の違うは何? (php.ini)
session.cookie_lifetime = 0
session.gc_maxlifetime = 1440
「session.cookie_lifetime」はブラウザが覚えるセッションIDの
寿命を設定する。
「0」の場合は、ブラウザを終了させるまで有効という意味だ。

「session.gc_maxlifetime」は、サーバー側に保管する
セッション変数の寿命を設定だ。

  ふと思った。
  実際に「session.gc_maxlifetime」の値を変更して
セッション変数が消去されるかどうか確かめる事にした。

  そこで以下のプログラムを作成した。

セッションIDとセッション変数を表示するプログラム
<?php

session_start();

$id = session_id();
$_SESSION[$id] = "test" ;

?>

<html><head><title>クッキーの実験</title></head>
<body>

<?php

echo $id ;
echo "<br>" ;

echo $_SESSION[$id] ;

?>

</body></html>
セッションIDと、セッション変数を表示させるプログラムだ。

  そして「session.gc_maxlifetime」の値を5秒にした。

セッション変数の寿命の設定

; After this number of seconds, stored data will be seen as 'garbage' and
; cleaned up by the garbage collection process.
session.gc_maxlifetime = 5

ブラウザが覚えるセッションIDの寿命も設定

; Lifetime in seconds of cookie or, if 0, until browser is restarted.
session.cookie_lifetime = 1


  これで、ブラウザにクッキーとして覚えさせたセッションIDは
1秒で忘れてくれる。

  そしてプログラムを実行してみた。

プログラムの実行結果
セッションの寿命を短縮させてみる

  5秒でセッション変数が消えるだろうと思い、プログラムを実行してから
5秒以上経過した後に、セッション変数を保管しているディレクトリーから
セッション変数が消えているかどうか確かめてみる。

セッション変数が保管されているディレクトリ
[suga@server tmp]$ ls
jd_sockV4                              sess_7ac3398d203df6e84a60e94529fd639b
orbit-root                             sess_90f0ecfb9658b10ad3743582608b1117
sess_1b4c839f93559c36da595bf3f5462ff3  sess_a5b41760058a4dc756c93a61e0ff1657
sess_1b760b395eea6b95fd89d41f022167e1  sess_a8610c3f5ba0ebbac61d6723d500e384
sess_25e115c8ad6cc583dabe22885e748984  sess_b50d87a867fb50e73f448a08a9ca7d70
sess_2c6e7f2375bab5c0ce6ea3ccc8b69225  sess_b657bf22ea6d62bd1264e8fa5e29eead
sess_45269d025d2483b4a77a4514cc5f214a  sess_bd403091ba987c8b9d6c464c15b43760
sess_4e8f631f5e01b806b3918aa0d07dcc23  sess_c161d703ef88e954304675b6dd4ebebb
sess_524531235dce78c5a7e904e63bd79dc4  sess_cac93dca20317d1a81507d4eec56cfc6
sess_7740b26cdfaf8bb6ed31ff143d806ebf  sess_df13e49b230e865594e8526c5bf58a59
sess_786b25eba2b4d3602806e0243f419a53  ssh-XXVFWBnW
[suga@server tmp]$

  全く消えてへんやん!

  プログラム実行後、生成されたセッション変数のファイルは、
生成されてから5秒後に消えるわけではない。

  もしかして、プログラムを再実行した際に、セッション変数が
寿命を迎えた物を消す仕組みだと思った。
  そこで、寿命の5秒が経過しているので、更新ボタンを押して、
プログラムを再実行を行った。
  さてファイル「sess_c161d703ef88e954304675b6dd4ebebb」は
消去されているかどうか確認をしてみた。

プログラムの実行結果
セッションIDの寿命が短いため、次の接続には新たなセッションIDが発行されている
ブラウザが覚えたセッションIDの寿命は、この実験の設定では
1秒しか寿命がない。
そのため、再実行の際、既にブラウザは先ほどサーバーから受け取った
セッションIDは忘れているので、サーバーから新たにセッションIDを
もらう事になる。

  さて、既にブラウザが忘れたセッションID(c161d703ef88e954304675b6dd4ebebb)の
セッション変数のファイル「sess_c161d703ef88e954304675b6dd4ebebb」は
消去されているかどうか確認をしてみた。

セッション変数が保管されているディレクトリ
[suga@server tmp]$ ls
jd_sockV4                              sess_7ac3398d203df6e84a60e94529fd639b
orbit-root                             sess_90f0ecfb9658b10ad3743582608b1117
sess_1b4c839f93559c36da595bf3f5462ff3  sess_a5b41760058a4dc756c93a61e0ff1657
sess_1b760b395eea6b95fd89d41f022167e1  sess_a8610c3f5ba0ebbac61d6723d500e384
sess_25e115c8ad6cc583dabe22885e748984  sess_b50d87a867fb50e73f448a08a9ca7d70
sess_2c6e7f2375bab5c0ce6ea3ccc8b69225  sess_b657bf22ea6d62bd1264e8fa5e29eead
sess_45269d025d2483b4a77a4514cc5f214a  sess_bd403091ba987c8b9d6c464c15b43760
sess_4e8f631f5e01b806b3918aa0d07dcc23  sess_c161d703ef88e954304675b6dd4ebebb
sess_524531235dce78c5a7e904e63bd79dc4  sess_cac93dca20317d1a81507d4eec56cfc6
sess_7740b26cdfaf8bb6ed31ff143d806ebf  sess_df13e49b230e865594e8526c5bf58a59
sess_786b25eba2b4d3602806e0243f419a53  ssh-XXVFWBnW
[suga@server tmp]$ 
[suga@server tmp]$ more sess_c161d703ef88e954304675b6dd4ebebb
c161d703ef88e954304675b6dd4ebebb|s:4:"test";
[suga@server tmp]$

  結果は・・・

  全然、消えてへんやん!

  何度も更新ボタンを押したりしたが、それでもセッション変数は消えず。
  セッション変数の寿命「session.gc_maxlifetime」の設定を
5秒にしているのも関わらず、いつまでも残っているのは何故だろうか・・・

  謎だ・・・ (--;;


  しかし、あとで理由がわかった。
  前後は逆になるが、先に、その謎を説明します。

  実は、「php.ini」には以下の設定項目がある。

ゴミ収集に関する設定(php.ini)

; Define the probability that the 'garbage collection' process is started
; on every session initialization.
; The probability is calculated by using gc_probability/gc_divisor,
; e.g. 1/100 means there is a 1% chance that the GC process starts
; on each request.

session.gc_probability = 1
session.gc_divisor     = 100

  英文を読む前に、日本語文の説明などを見つけた。

  この2つは一緒になって使う物だ。
  ゴミ収集に関する設定だ。

  ゴミ収集の処理はセッション開始時に行われる。
  セッションが開始する度、ゴミ収集の処理が走ると、少なからず処理が重くなる。
  接続数の少ないサイトだと、セッション開始の度にゴミ収集処理が走っても
全く問題にならないが、1分間に何百、何千もの接続があるサイトだと、
無視できない負荷になってくると考えられる。

大量に接続のあるサイトの場合
大量の接続時の状態
上図のように、大量に接続があるサイトの場合、
毎回、セッション開始時にゴミ収集処理を行っていたのでは
サーバーの負荷はバカにならなくなる。

  そこで負荷を軽くするために、セッション開始がある度ではなく、
何回か一度のセッション開始の割合で、ゴミ収集処理が走るようにするため
「session.gc_probability」と「session.gc_divisor」の設定を行う。

  この2つの値は以下のような形で、セッション開始の際、
ゴミ収集処理が行われる確率を設定するのに使われる。

セッション開始の際、ゴミ収集処理が行われる確率の設定
セッションの破棄される確率を求める式
「session.gc_probability」は分子の値で
「session.gc_divisor」は分母の値になる。

初期設定では「session.gc_probability」は「1」であり
「session.gc_divisor」は「100」のため、セッション開始時に
ゴミ収集処理が行われる確率は1%になる。

  さて、実際に確率通りにゴミ収集処理が行われるの実験してみる事にした。
  しかし、確率を0.5(50%)や、0.7(70%)にして確かめるのは面倒だ。
  そこで、確率を1(100%)にして、セッション開始がある度に
ゴミ収集処理が行われる設定をして、実際に不要になったセッション変数が
消去されているか確かめる事にした。

  まずは以下の設定を行った。

セッション開始がある度に不要なセッション変数を消去する設定
session.cookie_lifetime = 1
session.gc_maxlifetime = 1
session.gc_probability = 1
session.gc_divisor     = 1
ブラウザが覚えるセッションIDの寿命を1秒にした(赤い部分)
セッション変数の寿命を1秒にした。(青い部分)

そして、セッションが開始される度に、ゴミ収集処理を行う設定にした
(黒字の部分)

  そして、セッションIDと、セッション変数を表示させるプログラムを用意する。

セッションIDとセッション変数を表示するプログラム
<?php

session_start();

$id = session_id();
$_SESSION[$id] = "test" ;

?>

<html><head><title>クッキーの実験</title></head>
<body>

<?php

echo $id ;
echo "<br>" ;

echo $_SESSION[$id] ;

?>

</body></html>

  プログラム実行前に、セッション変数が格納されているディレクトリーを
綺麗さっぱりしておく。

セッション変数を保管するディレクトリ
[suga@server tmp]$ ls
jd_sockV4  orbit-root  ssh-XXVFWBnW
[suga@server tmp]$

  準備ができたので、プログラムを実行してみる。

プログラムの実行結果
まずはサーバーから発行されるセッションIDを受け取る
サーバーから「bf5d5cdaf8accc4f418d508bfbc3d3ba」の
セッションIDが割り当てられた

  セッション変数が格納されているディレクトリーを見てみる。

セッション変数を保管するディレクトリ
[suga@server tmp]$ ls
jd_sockV4  orbit-root  sess_bf5d5cdaf8accc4f418d508bfbc3d3ba  ssh-XXVFWBnW
[suga@server tmp]$
セッションIDが「bf5d5cdaf8accc4f418d508bfbc3d3ba」の
セッション変数が保管されている。

  さて、php.iniの設定では、セッション変数の保管を1秒にしたため
ファイル「sess_bf5d5cdaf8accc4f418d508bfbc3d3ba」はセッション終了後、
1秒経過すると不要なファイル(ゴミ)になってしまう。
  そして、セッション開始後、ゴミとなったセッション変数を消去するように
設定している。
  実際に、消去されるかどうかを確かめるため、もう一度、同じプログラムを
実行する事にした。

プログラムの実行結果
新規のセッションIDを受け取る
今度はサーバーから「7b2feec5a2331005a62bb821fb9481a1」の
セッションIDが割り当てられた

  さて、セッション変数が保管されるディレクトリの中を見てみる。

セッション変数を保管するディレクトリ
[suga@server tmp]$ ls
jd_sockV4  orbit-root  sess_7b2feec5a2331005a62bb821fb9481a1  ssh-XXVFWBnW
[suga@server tmp]$
最初に生成された「sess_bf5d5cdaf8accc4f418d508bfbc3d3ba」ファイルは
消去されている事が確認できる。

  予想通りの結果になった。

  やはり自分の目で確かると、よくわかる (^^)


セッションIDが有効なWebサイトのディレクトリの設定 次に、クライアントがサーバーに接続する際、セッションIDが 有効なパスの指定を行う。 通常、全てのパス(ディレクトリ)で有効になる、
セッションが有効なパス(php.iniの初期設定)

; The path for which the cookie is valid.
session.cookie_path = /

ホームページサーバーで、クッキーが有効なパスを指定。
このパス以下のディレクトリはクッキーが有効を示す。

  でも、一部のパスだけ有効にしたい場合もある。
  例えば、トップディレクトリから一段下がった「/ses2」ディレクトリだけ
セッションIDを有効にしたい場合は、以下のような設定を行う。

セッションが有効なパス(php.ini)

; The path for which the cookie is valid.
session.cookie_path = /ses2

ホームページサーバーで、クッキーが有効なパスを指定。
このパス以下のディレクトリはクッキーが有効を示す。

  さて、実際に有効なパスが変わっているかどうか、確かめる事にした。

有効なパスを見てみる (Firefox2.0.0.6)
ブラウザに記録されたサイトの有効パス
セッションIDが有効なパスが「/ses2」になった事がわかる。
これを使えば、必要なパスの部分だけセッションIDを有効にできる。


有効ドメインについて 同じWebサーバーでも、複数のドメインを持つ事ができる。 そこで複数のドメインの中から特定のドメインだけ、クッキーによる セッション管理を有効にする設定がある。
有効なドメインの設定(php.ini)

; The domain for which the cookie is valid.
session.cookie_domain =

初期設定では何も記述されていない。
そのため、Webサーバーが持っている全てのドメインで
クッキーによるセッション管理が有効にしている。


サーバー変数の「HTTP_REFERER」 サーバー変数の「HTTP_REFERER」に関する設定を見てみます。 と書いたものの、この編集を行う時・・・ HTTP_REFERERって何やねん? だった (^^;; そこで調べてみると以下の物を表示させるための変数だった。
サーバー変数(HTTP_REFERER)とは
クライアントが1つ前に見ていたサイトのURLを入れる変数だ。
ただし、リンクから辿った場合のみで、直接、URLを入力したり
お気に入りから接続した場合は表示されない。

つまり、リンク元を表示させるための変数になる。
俗語で「リファー」という。

  論より証拠。
  実際に、1つ前に見たサイトのURL、即ち、リンク元が表示されるかどうかを
確かめるため、以下のプログラムを走らせてみる。

1つ前のサイトのHTML(ref.html)
<html><head><title>HTTP_REFERER 実験前</title></head>
<body>
 
<a href="ref.php">ここをクリック</a>
 
</body></html>
「HTTP_REFERER」を使って実際にref.htmlからリンクで
辿ってきた事を示すプログラム (ref.php)
<?php

// このページを開く1つ前のサイトのURLを取り出す
$ref = $_SERVER['HTTP_REFERER'] ;

?>
 
<html><head><title>HTTP_REFERER 実験結果</title></head>
<body>
 
1つ前に見たサイト(URL) : <? print $ref ?>
 
</body></html>

  さて、プログラムの実行結果を見てみる。

ref.htmlを開いてみる
該当のHTMLを開いてみる
上の図は、ref.htmlを開いてみた結果だ。
そして、リンク先をクリックする。
ref.htmlからリンクでref.phpを見た結果
目的の「HTTP_REFERER」サーバー変数が出てきた
赤く囲んだ部分がref.phpを開いた際、リンク元のref.htmlが
キチンと表示されているのがわかる。

  この「HTTP_REFERER」サーバー変数を使うと、リンク元のサイトが
わかるというわけだ。

「REFERER」の単語の意味を調べてみると
「REFERER」の単語の意味を調べてみる事にした。
その原型の「refer」は「言及する」、「参照する」の意味だ。
「refer」は「re」と「fer」という形に分解できる。
「re」は「再び」、「後ろに」という接頭辞で
「fer」は「運ぶ」の意味がある。

そう考えると「REFERER」は「後ろに運ぶ人」や「後ろに運ぶ物」
という事で、リンク元という意味を当てている。

「リファー」というカタカナにしてしまうと
何の事か意味がわからなくなってしまうが、
単語を調べてみると、ある程度、納得ができたりする。

  リンク元だけが表示される。
  しかも、お気に入りや、直接、URLを入力した場合では、
1つ前に見たサイトの情報は流れない。
  これには、大きな利点がある。なぜなら・・・

  エロサイト見た後でも、第三者にバレないのらー!!

  そう。
  例えば「エロエロ特集」とか「エロ画像公開!」というページを見た事が
第三者にわかると、エロオヤジ丸出しで格好の悪いのだ (^^;;


  さて本題に戻ります。

  php.iniの設定ファイルで、サーバー変数(HTTP_REFERER)を使った設定だが、
リンク元のURLに含まれている文字列を見て、セッション管理を起動させるかを
判断する設定だ。

サーバー変数(HTTP_REFERER)を使った設定
(php.ini)

; Check HTTP Referer to invalidate externally stored URLs containing ids.
; HTTP_REFERER has to contain this substring for the session to be
; considered as valid.
session.referer_check =

初期設定では空白になっているため、リンク元のURLが何であっても
セッション管理を起動させる意味になっている。

  そこでリンク元のURLに含まれている文字列で、セッション管理が
起動するのかどうか、実際に確かめる事にした。

  まず、php.iniの設定を次のようにした。

サーバー変数(HTTP_REFERER)を使った設定

; Check HTTP Referer to invalidate externally stored URLs containing ids.
; HTTP_REFERER has to contain this substring for the session to be
; considered as valid.
session.referer_check = ref2

リンク元のURLに「ref2」の文字列が含まれている場合
セッション管理を起動させる意味だ。

  次のプログラムを動かしてみる事にした。

リンク元のHTML (ref2.php)
<?php

session_start();
$id = session_id();

//  セッション変数を覚えさせる。
$_SESSION['test'] = "HTTP_REFERER 実験" ;

?>
 
<html><head><title>HTTP_REFERER 実験前</title></head>
<body>
 
<a href="ref3.php">ここをクリック</a>
 
</body></html>
「HTTP_REFERER」を使って実際にref2.phpからリンクで
辿ってきた事を示すプログラム (ref3.php)
<?php

session_start();
 
$ref = $_SERVER['HTTP_REFERER'] ;

?>
 
<html><head><title>HTTP_REFERER 実験結果</title></head>
<body>
 
1つ前に見たサイト(URL) : <? print $ref ?>

<br>

セッション変数<br>

<? print $_SESSION['test'] ?>

</body></html>
プログラムの流れとしては、ref2.phpでセッション変数を覚えさせる。
そしてリンクで ref3.php のプログラムを実行させる。
ref3.php でセッション管理を起動させて、ref2.phpで覚えさせた
セッション変数を呼び出す。

  ref3.phpのプログラムでは、リンク元のURLに「ref2」という
文字列が入っていれば、セッションが開始される仕掛けだ。
  リンク元のURLは「http://******/ref2.php」なので
「ref2」の文字列が含まれている。

  プログラムの実行結果は

ref2.php を動かした場合
まずはブラウザを開く
ref3.php を動かした結果
セッション変数の表示がされた様子
ref2.php で覚えさせたセッション変数を表示させる事ができた。
(青い部分)

ちなみに赤い部分は、リンク元のURLです。

  では、次にリンク元のURLに該当の文字列が含まれていない場合は
セッションが開始されなくあるのだろうか。

  そこで、php.iniの設定を以下にしてみた。

サーバー変数(HTTP_REFERER)を使った設定

; Check HTTP Referer to invalidate externally stored URLs containing ids.
; HTTP_REFERER has to contain this substring for the session to be
; considered as valid.
session.referer_check = aaaaaaaaaa

リンク元のURLに「aaaaaaaaaa」の文字列が含まれている場合
セッション管理を起動させる意味だ。

  それで、先ほどと同じように、ref2.phpと、そこからリンクで辿れる
ref3.phpを動かしてみる事にする。
  もちろん、ref2.phpのURLには「aaaaaaaaaa」の文字列が含まれていないのだ。

ref2.php を動かした場合
セッション管理をさせない仕掛けで
ref3.php を動かした結果
セッション管理が動かなかった
ref2.php で覚えさせたセッション変数を表示されなかった。
(青い部分)

これはセッション管理が起動していない事を意味する。

  思った通りに実験が成功した。

  さて、この設定は何のためにあるのか。
  特定のリンク元以外からの接続の場合、セッション管理を起動させないための
設定だという。

  色々、調べてみたが・・・

  イマイチよくわからん (--;;

  なのだ。
  ここは「事務員なので、わかりませーん」で逃げたいと思います♪

セッション変数の保管

セッション変数の保管の話を書きます。 今までの説明では、ハードディスク上の指定したディレクトリに 保管される話を書きました。
セッション変数を保管するディレクトリの設定

; Argument passed to save_handler.  In the case of files, this is the path
; where data files are stored. Note: Windows users have to change this 
; variable in order to use PHP's session functions.
; As of PHP 4.0.1, you can define the path as:
;     session.save_path = "N;/path"
; where N is an integer.  Instead of storing all the session files in 
; /path, what this will do is use subdirectories N-levels deep, and 
; store the session data in those directories.  This is useful if you 
; or your OS have problems with lots of files in one directory, and is 
; a more efficient layout for servers that handle lots of sessions.
; NOTE 1: PHP will not create this directory structure automatically.
;         You can use the script in the ext/session dir for that purpose.
; NOTE 2: See the section on garbage collection below if you choose to
;         use subdirectories for session storage
;session.save_path = /tmp

初期設定では上のピンクの部分は「;」で無効になっているが、
この状態の場合、「/tmp」ディレクトリに、セッション変数が
保管される設定になっている。
ピンクの部分に、セッション変数を保管したい先の
ディレクトリを指定する事で、指定したディレクトリーに
セッション変数を保管する事ができる。

  でも、セッション変数の保管はハードディスクだけではないのだ。

  メモリ上に保管する事もできるのらー!

  セッション管理の設定に関して調べていたら、セッション変数を
メモリ上で保管できる事を知ったのだ。

セッション変数をハードディスク上に保管
セッション変数をハードディスク上に保管
セッション変数をメモリ上に保管
セッション変数をメモリ上に保管

  メモリ上に保管といっても、個々のPHPのプログラムが

  mmライブラリという共有メモリ用のライブラリを活用した
共有メモリ上でセッション変数が保管されるのだ。

共有メモリ上でセッション変数が保管される
共有メモリ上でセッション変数が保管される
共有メモリ上に保管する事で、複数のPHPのプログラムが
メモリ上のセッション変数を共有する事ができる。

  メモリ上で保管する時の利点・欠点がある。
  思いつくだけ挙げてみる事にした。

メモリ上に保管する利点
(1) メモリ上にデータがあるため
高速でデータの取り出しが可能。
メモリ上に保管する欠点
(1) セッション変数が多いとメモリを食う
(2) 停電など、不意に電源が切れたら
データの全て消える

  さて、お話だけで終わらせる事もできるのだが、この原稿を書いている
私自身が・・・

  自分の目で確かめたいのらー (^^)

  なので、実際に実験してみる事にした。

  mmライブラリを使うには、PHPのコンパイルで、configureを行う際に
「--with-mm」のオプションを付ける必要がある。

PHPのconfigureを行う際の注意点

configure --with-mm

「--with-mm」のオプションを付ける必要がある。
mmライブラリがインストールされていない場合は
mmライブラリのインストールも必要になる。

  コンパイルが終了したら、次は設定ファイル「php.ini」の設定を行う。

php.iniの設定では

; Handler used to store/retrieve data.
session.save_handler = mm

初期設定ではハードディスクに保管するため「files」になっているが
共有メモリ上に保管させる場合は「mm」にする。

  これで準備完了。

  早速、以下のプログラムを動かしてみる事にした。

プログラム
<?php

session_start();

$id = session_id();

$_SESSION['test'] = "Share Memory stored" ;

?>

<html><head><title>mm lib</title></head>
<body>

セッションID : <? print $id ?>

<br>

セッション変数 : <? print $_SESSION['test'] ?>

</body></html>
上のプログラムは、ブラウザ上にセッションIDと
セッション変数の値の表示をさせる物だ。

  そして、初期設定の場合、ハードディスクに保管されるセッション変数の
ディレクトリーを綺麗にしておく。

初期状態でセッション変数を保管するディレクトリ
[suga@server tmp]$ ls
jd_sockV4  orbit-root  ssh-XXVFWBnW
[suga@server tmp]$

  では、実際にプログラムを実行させてみる。

プログラムの実行結果
共有メモリ上でセッション変数を保管した場合
セッションIDとセッション変数が表示された。

  さて、実際にハードディスクではなく、共有メモリ上にセッション変数が
保管されているかどうか確かめるため、まずはハードディスクに保管される時の
ディレクトリーを見てみる。

ディレクトリを見てみる
[suga@server tmp]$ ls
jd_sockV4  orbit-root  ssh-XXVFWBnW
[suga@server tmp]$
ハードディスク上にセッション変数が保管されていない事が
一目でわかる。

  さて、共有メモリ上に保管されている事を証明すべく、
共有メモリ上のイメージを取り出して確認してみたいのだが

  そんな技術力があれば転職していまーす (^^)

  なのだ。

  そこで状況証拠だけでもと思い、ipcsコマンドを使う事にした。
  ipcsコマンドとは、IPCリソースを見るコマンドで、共有メモリ、
IPCセマフォー等の状態を見る事ができる。

  PHPは、Apacheのモジュールなので、Apacheが起動する前、
起動中、起動を止めた後の3つの、それぞれの共有メモリの状態を
見比べてみると良いのではないかと考えた。

  そこで見比べてみる事にした。

Apache起動前
[root@server]# ipcs -m
 
------ 共有メモリセグメント --------
キー       shmid      所有者     権限       バイト     nattch     状態
0x00000000 32768      root      644        98304      4          対象
0x0052e2c1 1802243    postgres  600        29237248   2
0x7902c151 1900548    root      666        404        0
0x043c8de2 2162693    root      666        536        0
0x033d83de 2228231    root      666        1          0
0x00000000 2981897    root      644        98304      3          対象
キーが6つある。
Apache起動中
[root@server]# ipcs -m

------ 共有メモリセグメント --------
キー       shmid      所有者     権限       バイト     nattch     状態
0x00000000 32768      root      644        98304      4          対象
0x00000000 3571713    root      600        1056768    6          対象
0x00000000 3604482    root      600        33554432   6          対象
0x0052e2c1 1802243    postgres  600        29237248   2
0x7902c151 1900548    root      666        404        0
0x043c8de2 2162693    root      666        536        0
0x00000000 3637254    root      600        33554432   6          対象
0x033d83de 2228231    root      666        1          0
0x00000000 3670024    root      600        46084      6          対象
0x00000000 2981897    root      644        98304      3          対象
キーの数が10個に増えた。
おそらくセッション変数を保管するための共有メモリの
影響だと思われる。
Apache起動を中断
[root@server]# ipcs -m

------ 共有メモリセグメント --------
キー       shmid      所有者     権限       バイト     nattch     状態
0x00000000 32768      root      644        98304      4          対象
0x0052e2c1 1802243    postgres  600        29237248   2
0x7902c151 1900548    root      666        404        0
0x043c8de2 2162693    root      666        536        0
0x033d83de 2228231    root      666        1          0
0x00000000 2981897    root      644        98304      4          対象
キーの数が6つ、Apacheの起動前の状態に戻った。

  これだけだと確固たる証拠ではないため「共有メモリを使っているかも」
といった感じで推測の域を抜けない。
  でも、PHPの仕様上、共有メモリを使うのはハッキリしているため、
これで納得するのも良いかも (^^;;


PHP5への移行の話 2007年12月31日で、PHP4のサポートが終了する情報が流れた。 PHPの本家のサイトには、PHP5.2に移行するように書いてあった。 普段、私はPHP4を使っているだけに、移行の準備の必要性を感じた。 もちろん、この内容を書く際に使ったPHP言語も、PHP4なのだ。 でも、私が書いている内容だと、バージョン互換のない関数は 使っていないと思うので、PHP5であっても問題ないと思います。
まとめ クッキーとセッション管理ですが、Webアプリのプログラムを組んでいた割には 今まで、ほとんど知りませんでした (^^;; クッキーの脆弱性を突いたXSS(クロスサイトスクリプト)をはじめ、 Webセキュリティーの問題があります。 今回は、セッションハイジャックに触れましたが、近いうちに、 Webセキュリティーの話も載せたいと思います。

次章:「PostgreSQL入門。ログ、WAL、PITR、障害時のデータ復旧を読む

前章:「HTML、CSSって何?」を読む

目次:システム奮闘記に戻る

Tweet