1年くらい前に社内MLに投げた、「MySQLでSET NAMESを使ってはいけない理由」をコピペしてみます。手抜きです、はい。
赤字は注釈です。
今更ながら、「MySQLで SET NAMES を使ってはいけない」の根拠のお話です。
下記のPHPスクリプトでは、入力値を元にSQL文を生成し、検索クエリを投げています。
※sqltestというDBには、カラムname
を持つuser
テーブルが存在します。
GETで渡された値はきちんとmysql_real_escape_string
をかけているので、SQLインジェクションは出来ないように見えます。
しかし、http://localhost/sqltest/index.php?name=%95%5c'%20OR%201=1%20--%20
にアクセスすると、全部のデータが見えてしまいます。
下にあるPHPスクリプトを、localhost/sqltest/index.php
として配置してください。
SET NAMES SJIS
を実行すると、MySQLのエンコードがShift-JISになりますが、mysql_real_escape_string
はUTF-8のまま動作します。
16進数で 95 5c 27 20
は、
- UTF-8: (謎の文字)(バックスラッシュ)(シングルコーテーション)(スペース)
- Shift-JIS: (表)(シングルコーテーション)(スペース)
になります。
mysql_real_escape_string
は、バックスラッシュとシングルコーテーションそれぞれをエスケープします。
95 5c 5c 5c 27 20
MySQLは、Shift-JISとして動作するので、 (表)(\
)(\
)(シングルコーテーション)(スペース) と認識します。
つまり、\
が一つ余分に入ることで、入力値のシングルコーテーションがエスケープされなくなります。
マルチバイト非対応のエスケープ関数を使うのと同じ理屈で、SET NAMES
は危険です。
基本的に文字コード中に5Cが入るShift-JISが危険ですが、他の文字コードでも似たようなことが起こる可能性があります。
mysql_set_charset('SJIS');
なら、mysql_real_escape_string
もShift-JISとして動作するようになるので、安全です。
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>SQLテストページ</title>
<head>
</head>
<body>
<form action="<?php echo $_SERVER['SCRIPT_NAME'];?>">
<input type="text" name="name" />
<input type="submit" value="search" />
</form>
<?php
//DBに接続
if (! $db = mysql_connect('localhost', 'sqltest', 'sqltest'))
{
echo 'CONNECT ERROR';
exit;
}
mysql_select_db('sqltest', $db);
mysql_query('SET NAMES SJIS');
// mysql_set_charset('SJIS');
//SQLを生成
$name = mysql_real_escape_string($_REQUEST['name']);
$sql = "SELECT * FROM user WHERE name='$name'";
echo $sql . '<br />';
//実行
if (! $res = mysql_query($sql))
{
echo "QUERY ERROR <br />";
echo mysql_error();
exit;
}
echo "<pre>";
while ($row = mysql_fetch_array($res)) {
print_r($row);
}
echo "</pre>";
?>
</body>
</html>