tanaka's Programming Memo

プログラミングについてのメモ。

PDOのprepareでdrop tableに失敗する

keyword: PDO , prepare , drop table

PHPからMySQLを操作するのに、PDO(PHP Data Objects)を利用している。SQLの安全動作のために、prepare()を使おうとしたが、drop tableで権限エラーが発生した。その原因と対策をメモ。

バージョン

  • MySQL4.0
  • PHP5.2.17

MySQLが古いので、最新版ならこの問題は発生しないかも。

症状

$db = new PDO('mysql:host=ホスト;dbname=データベース名' , ユーザ名 , パスワード);
$stmt = $db->prepare('drop table if exists :tblname');
$stmt->execute(array(':tblname' => テーブル名);

以上のようにすると、execute()がエラー42000を発生させる。

原因

drop tableでテーブル名を指定する際に、クォートでテーブル名を囲むとエラーが発生する。prepare()+execute()では、自動的にパラメータをクォートで安全に囲むようになっているため、エラーとなってしまった。

対策

もともとprepare()は、ステートメントを事前にデータベース側でコンパイルして、実行時にパラメータを差し替えながら渡すことで高速な実行をするためのものである。drop tableは連続で実行するものではないので、prepare()を利用する必要はない。よって、PDO::exec()で直接コマンドを実行する。

テーブル名を外部から指定する場合、クォートで囲んで不正なデータベースコマンドの実行を抑制したい。そのような処理にはPDO::quote()が使えるが、これをやると同様にセキュリティエラーが発生する。そこで、事前にprepare()が使える命令で引数が正しいことを検証してからdrop tableを実行するようにすればよい。

例えば、事前に以下のようにテーブルを検索して、テーブルが見つかればテーブル名は問題ないので、drop tableを実行するという形にするなど。

$stmt = $db->prepare('show tables from データベース名 like :tblname');
$stmt->execute(array(':tblname' => テーブル名));
if ($stmt->rowCount() > 0) {
  // テーブル名は有効なので、テーブル削除を実行
  $query = "drop table if exists ".テーブル名;
  $db->exec($query);
}