PHPとMySQLのセキュリティー対策関数
※以前別の場所で書いた文章を備忘的に書き記しておきます。
【投稿年月日】2008-02-26 【ジャンル】PHP/MySQL
最近必要に迫られて、昔書いたプログラムの見直し作業をやっています。MySQLの処理の高速化がメインなのですが、それと並行してセキュリティチェックも行なっています。PHPでは、クロスサイトスクリプティング対策には「htmlspecialchars()」を、MySQLのSQLインジェクション対策には「mysql_real_escape_string()」を使って、変数を安全なものにするのですが、これが美しくないし、様々な不都合が生じる場合があります。
と言う訳で、セキュリティ対策用に汎用の関数を作成してみました。ポイントは以下の通り。
- 配列ごと一気に変換できる。
- クラス内でも使える。
- get_magic_quotes_gpc()の設定に関係なく使える。
- PHP4.3未満でも使える。
- 覚えやすい関数名なので手軽に使える。
●セキュリティ対策用関数(*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;
}
}
?>
// 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));
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.'"');
}
$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