PHPとMySQLのセキュリティー対策関数

※以前別の場所で書いた文章を備忘的に書き記しておきます。

【投稿年月日】2008-02-26 【ジャンル】PHP/MySQL

 最近必要に迫られて、昔書いたプログラムの見直し作業をやっています。MySQLの処理の高速化がメインなのですが、それと並行してセキュリティチェックも行なっています。
 PHPでは、クロスサイトスクリプティング対策には「htmlspecialchars()」を、MySQLのSQLインジェクション対策には「mysql_real_escape_string()」を使って、変数を安全なものにするのですが、これが美しくないし、様々な不都合が生じる場合があります。

 と言う訳で、セキュリティ対策用に汎用の関数を作成してみました。ポイントは以下の通り。
  1. 配列ごと一気に変換できる。
  2. クラス内でも使える。
  3. get_magic_quotes_gpc()の設定に関係なく使える。
  4. PHP4.3未満でも使える。
  5. 覚えやすい関数名なので手軽に使える。

●セキュリティ対策用関数(*XSS及びSQLインジェクション)

<?php
// XSS
 function h($str='') {
  if(is_array($str)) {
   $h = function_exists("h") ? "h" : array(&$this, "h");
   return array_map($h, $str);
  }else {
   if(!is_numeric($str)) {
    $str = htmlspecialchars($str, ENT_QUOTES, "UTF-8"); // 文字コードは適宜変更
   }
   return $str;
  }
 }
// SQL Injection
 function q($str='') {
  if(is_array($str)) {
   $q = function_exists("q") ? "q" : array(&$this, "q");
   return array_map($q, $str);
  }else {
   if(get_magic_quotes_gpc()) {
    $str = stripslashes($str);
   }
   if(!is_numeric($str)) {
    $ver = explode('.', phpversion());
    if(intval($ver[0].$ver[1])>=43) {
     $str = mysql_real_escape_string($str);
    }else {
     $str = addslashes($str);
     $pre = array('/\n/m', '/\r/m', '/\x1a/m');
     $after = array('\\\n', '\\\r', '\Z');
     $str = preg_replace($pre, $after, $str);
    }
   }
   return $str;
  }
 }
?>

 参考にしたサイトは以下の通り。
 なお、変数(配列)を数値に限定する場合は、上記セキュリティ対策関数ではなく「intval()」を使います。安全なスクリプトを記述する第一歩が、この3種類を使い分けることだと思います。

 簡単なサンプル。$linkと$queryは文字列、$idは数値。
//HTML出力時
 echo '<a href="'.h($link).'">'.h($query).'</a>';
//データベース操作時
 mysql_query('SELECT * FROM d1 WHERE q = "'.q($query).'" and id = '.intval($id));
//HTML出力時(※クラス内)
 echo '<a href="'.$this->h($link).'">'.$this->h($query).'</a>';
//データベース操作時(※クラス内)
 mysql_query('SELECT * FROM d1 WHERE q = "'.$this->q($query).'" and id = '.intval($id));

 HTML出力時とデータベース操作時で明確に使い分ける必要があります。上記サンプルでは、両者共に変数$queryをサニタイズしていますが、上記セキュリティ対策関数を逆に使用した場合、変換される文字が違うのでセキュリティ対策にはなりません。
 よって、上記セキュリティ対策関数を使うタイミングは、$_GETや$_POST、グローバル関数等で変数を受け取った直後ではなく、HTML出力する直前か、データベース操作直前がベストだと言えます。詳しくは「PHPエスケープ関数の比較一覧」をご覧下さい。

 また、配列(連想配列や多元配列を含む)の場合でも使用は1回でOKです。以下、配列$array_codeでのサンプル。配列(変数)の名前付けなどについて詳しくは「PHPとMySQLのセキュリティー対策関数の継承」をご覧下さい。
//HTML出力時
 $h_array_code = h($array_code);
 foreach($h_array_code as $h_link=>$h_query) {
  echo '<a href="'.$h_link.'" title="'.$h_query.'">'.$h_query.'</a>';
 }
//データベース操作時
 $q_array_code = q($array_code);
 foreach($q_array_code as $q_link=>$q_query) {
  mysql_query('SELECT * FROM d1 WHERE q = "'.$q_query.'" and l = "'.$q_link.'"');
 }

【参考】

EDIUNET | PHP/MySQL | 独り言 | 提供サービス | JavaScript