安全なプリペアードクエリ

PDO を使ってデータベース操作を行うときの、プリペアードクエリに少し躓いたのでまとめておこうと思います。

参考サイト:

PDO (PHP Data Object) とは

PHP の中で DB に接続し、操作を行うためのインターフェースです。DB の種類ごとに異なる関数を使う必要がなく、使用する DB の種類を変更する際にも対応しやすいという利点があります。

プリペアードクエリ とは

PDO が提供する、DB へ送る SQL 文を2段階に分けて実行する手法です。1段階目では、 SQL 文を 解析・コンパイル・最適化 し、2段階目で実行します。また、1段階目でパラメータをプレースホルダに置き換え、2段階目でそのプレースホルダにパラメータを渡すことが可能です。

プリペアードクエリを使うメリットは次の通りです。

  • SQL 文の解析~最適化は最初の1回だけ行えばよく、その SQL を何回も行うときに高速な動作が期待できる。
  • SQL 文の構成に入力値を使うときSQL インジェクションの危険があるが、適切にプレースホルダを用いることで容易にインジェクション対策ができる。

今回は、2つ目のインジェクション対策に焦点を当てます。

実際に使ってみる

今回はテキストボックスへの入力を受けて、下のテーブルから値を取り出し名前を表示するようにします。「きゅうり」を受け取ったら「きゅうり」を表示するという単純なものです。

+----------+-------+
| name     | price |
+----------+-------+
| キャベツ |   200 |
| きゅうり |    80 |
| かぼちゃ |   300 |
+----------+-------+

まず、プリペアードクエリを使わない方法でやってみます。コードは以下の通りです。

$sql = 'SELECT name FROM food WHERE name="'.$_POST['food'].'"';
    $foods = $db->query($sql)->fetchALL(PDO::FETCH_ASSOC);
    foreach ($foods as $food) {
        foreach ($food as $name) {
            print $name;
        }
    }

入力を行った結果を見てみます。

入力:「きゅうり」

入力:「きゅうり” or name=”キャベツ」

インジェクション対策を行ってないので、意図的に SQL 文を書き換えられています。

次に、プリペアードクエリを使いますが、入力値をプレースホルダで置き換えずにやってみます。コードは以下の通りです。

$sql = 'SELECT name FROM food WHERE name="'.$_POST['food'].'"';
    $stmt = $db->prepare($sql);
    $stmt->execute();
    $foods = $stmt->fetchALL(PDO::FETCH_ASSOC);
    foreach ($foods as $food) {
        foreach ($food as $name) {
            print $name;
        }
    }

結果を見てみます。

入力:「きゅうり」

入力:「きゅうり” or name=”キャベツ」

プリペアードクエリを使ったのにインジェクションされています。

それでは、プレースホルダを使ったプリペアードクエリを試してみましょう。コードは以下です。

$sql = 'SELECT name FROM food WHERE name=?';
    $stmt = $db->prepare($sql);
    $stmt->execute(array($_POST['food']));
    $foods = $stmt->fetchALL(PDO::FETCH_ASSOC);
    foreach ($foods as $food) {
        foreach ($food as $name) {
            print $name;
        }
    }

結果を見てみます。

入力:「きゅうり」

入力:「きゅうり” or name=”キャベツ」

ここだけ $foods を var_dump() してます。

インジェクション を防げました!

まとめ

今回は、

  • プリペアードクエリを使わない通常クエリ
  • プレースホルダを用いないプリペアードクエリ
  • プレースホルダを用いたプリペアードクエリ

の3通りについて試し、プレースホルダを用いたプリペアードクエリのみがインジェクションを防ぐことができました。prepare() でクエリを解析した後にプレースホルダに値を渡すので、不適切な入力を防いでくれています。

なので、入力値を用いて DB を操作する際には、プレースホルダを用いてプリペアードクエリを使うのがほぼ必須になります。構文によってどうしても難しい場合は、かなり厳格な入力バリデーションが必要になります。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です