TOP > 読み物 > PHP: ぴゅーっと速くプログラミング > フレームワークを作ってみよう(5)

PHP: ぴゅーっと速くプログラミング

フレームワークを作ってみよう(5)

潔癖症で行こう

冷夏だ残暑だ阪神優勝だと言ってる間に気がついたら前回から一ヶ月が経ってしまってました。すいません。もうちょっと速いペースで書けるようがんばります。

ところで今までのサンプルはセキュリティ的にあんまりよくないです。 環境によっては所謂クロスサイトスクリプティングができてしまいます。 クロスサイトスクリプティングというのはHTMLにこっそりと埋め込んでおいて悪いことしたりするアレです。 試しに前回のサンプルで名前やメールアドレスのところに と書いてみて送信してみてください。メッセージが出ましたか? これはあんまりよくないですね(使い方にもよりますが…)。

このスクリプトの問題をもう少しわかりやすい例で見てみましょう。 例えば同じページで とかって入れてみてください。全く期待していない出力になりましたよね?

これは次の二つの原因によるものです。

  1. <>などのHTMLとして解釈される特殊文字をエスケープしていない
  2. PHPの設定で magic_quotes_gpc が有効になっているため、'や"eがエスケープされてしまっている

一つ目は単に htmlspecialchars() してない、というだけですね。 二つ目はPHPの設定によるものなので、環境によってはおかしくならないところもありますが、逆に言うと環境に左右されないようにクラスを作っておかないとと、いうことになります(もちろん個人的に使う分には決め打ちでも構いませんが)。ちなみにこのような不正な入力値について処理を行うことをサニタイジング(sanitizing)と言います(二つ目は微妙にニュアンス違うかも)。消毒とか除菌とかいう意味です。

ということでクラス定義を次のように改造します。

framework.php: download source

  1: <?php
  2: /*
  3:  * framework.php
  4:  *   Copyright (C) YAMADA Satoshi
  5:  */
  6: 
  7: define('MYSELF',  $_SERVER['PHP_SELF']);
  8: 
  9: class
 10: framework
 11: {
 12:   function
 13:   framework()
 14:   {
 15:   }
 16: 
 17:   function
 18:   import($array)
 19:   {
 20:     $this->set($_GET, $array);
 21:     $this->set($_POST, $array);
 22:   }
 23: 
 24:   function
 25:   set($hash, $array)
 26:   {
 27:     $mq = ini_get('magic_quotes_gpc');        // 追加
 28:     foreach ($array as $name)
 29:       if (isset($hash[$name])) {        // この中変更
 30:         $this->$name      = ($mq)? stripslashes($hash[$name]): $hash[$name];
 31:         $this->{"_$name"} = htmlspecialchars($this->$name);
 32:       }
 33:   }
 34: 
 35:   function
 36:   main()
 37:   {
 38:     $action = addSlashes($this->action);        // 追加
 39:     if (method_exists($this, $this->action)) $this->{$action}();
 40:     include "$action.html";
 41:   }
 42: }
 43: 
 44: // vi:ts=8 sw=2
 45: ?>

27行目が追加になって、さらに 29行名のif文の中が変わっています。 何をやっているかと言うと、27行目でmagic_quotes_gpc設定のチェック、 30行目でそれを反映しつつ設定、さらに31行目では表示用として、 フォーム変数名の頭に"_"をつけた形で別にクラス変数を設定しています。

つまり例えば $this->text が "<CHECK THIS OUT>" なら $this->_text は "&lt;CHECK THIS OUT&gt;"となるわけです。

ただし、一点注意が必要です。以前少し説明したように、このスクリプト(framework.php)は元々PHPの設定によってはセキュリティホールとなっていたのですが、今回の対応でPHPの設定に関わらずセキュリティホールとなってしまいます。所謂NullByteAttackというやつです。詳しくは重松さんによる解説ページをご覧ください。

30行目でエスケープされてたらそれを取り除く、ということをやっているので、 magic_quote_gpcの設定を無意味にしてしまってます。 そこで38行目で改めてエスケープし直しています。 あんまり綺麗な解決方法ではないんですがとりあえずということで。

で、今回はサンプルソースとしては変更はありません。前回のままです。

sample.php: download source

  1: <?php
  2: 
  3: require('framework.php');
  4: 
  5: class
  6: sample extends framework
  7: {
  8:   function
  9:   sample()
 10:   {
 11:     $this->class_vars = array(
 12:       'action',
 13: 
 14:       'name',
 15:       'email',
 16: 
 17:       'ok',
 18:       'cancel',
 19:     );
 20:     $this->import($this->class_vars);
 21: 
 22:     if (!$this->action) $this->action = 'form';
 23:   }
 24: 
 25:   function
 26:   form()
 27:   {
 28:     $this->name  = '名前';
 29:     $this->email = 'メールアドレス';
 30:   }
 31: 
 32:   function
 33:   check()
 34:   {
 35:     if (!$this->name) {
 36:       $err[] = '名前を入力してください';
 37:     }
 38:     if (!$this->email) {
 39:       $err[] = 'メールアドレスを入力してください';
 40:     }
 41:     if ($err) {
 42:       $this->error = '<ul><li>'. implode('<li>', $err). '</ul>';
 43:       return 0;
 44:     }
 45:     return 1;
 46:   }
 47: 
 48:   function
 49:   confirm()
 50:   {
 51:     if (!$this->check()) {
 52:       $this->action = 'form';
 53:       return;
 54:     }
 55:   }
 56: 
 57:   function
 58:   done()
 59:   {
 60:     if (!$this->check() || $this->cancel) {
 61:       $this->action = 'form';
 62:       return;
 63:     }
 64: 
 65:     // 例えば...
 66:     // ・mb_send_mail($this->email, $subject, $body);
 67:     // ・INSERT INTO member (name, email) VALUES ($this->name, $this->email);
 68:   }
 69: }
 70: 
 71: $sample = new sample();
 72: $sample->main();
 73: 
 74: // vi:ts=8 sw=2
 75: ?>

その代わり、各テンプレートの変数のところを 先程追加した処理で用意した表示用の変数とします。 各メンバ変数の名前の頭に"_"がついてるのがおわかりいただけるかと思います。

form.html: download source

  1: <h1>フォーム</h1>
  2: 
  3: <?php if ($this->error): ?>
  4: <p><strong><font color="red"><?= $this->error ?></font></strong></p>
  5: <?php endif ?>
  6: 
  7: <form action="<?= MYSELF ?>" method="POST">
  8: <input type="hidden" name="action" value="confirm" />
  9: 名前: <input type="text" name="name" value="<?= $this->_name ?>"><br />
 10: メールアドレス: <input type="text" name="email" value="<?= $this->_email ?>"><br />
 11: <input type="submit" value="次へ" />
 12: </form>

confirm.html: download source

  1: <h1>確認</h1>
  2: 
  3: <form action="<?= MYSELF ?>" method="POST">
  4: <input type="hidden" name="action" value="done" />
  5: <input type="hidden" name="name" value="<?= $this->_name ?>" />
  6: <input type="hidden" name="email" value="<?= $this->_email ?>" />
  7: 名前: <strong><?= $this->_name ?></strong><br />
  8: メールアドレス: <strong><?= $this->_email ?></strong><br />
  9: <input type="submit" name="ok" value="次へ" />
 10: <input type="submit" name="cancel" value="戻る" />
 11: </form>

done.html: download source

  1: <h1>完了</h1>
  2: 
  3: 完了しました。<br />
  4: 名前: <strong><?= $this->_name ?></strong><br />
  5: メールアドレス: <strong><?= $this->_email ?></strong>

最初に試したような変な値をいろいろ入力して試してみてください。 [ 実行例 ]

今回はこの辺で。次回はいつになることやら…


[ 前へ | 次へ ]