web-dev-qa-db-ja.com

バインドするパラメーターの数が可変の準備済みステートメントの実行

私が使用するいくつかのストアドプロシージャでは、プロシージャの入力パラメータが指定されているかどうかに基づいてWHERE条件を補間する必要があります。潜在的な注入ポイントを回避するために、補間された基準の一部となる値にパラメーターバインディングを利用したいと思います。

準備済みステートメントに追加された基準、つまりバインドされるパラメーターの数はユーザー入力によって異なる場合があるため、EXECUTEステートメントに渡される変数を決定するために、以下の方法を考案しました。これは機能しますが、洗練されていないようです。

CREATE PROCEDURE foo (IN mandatory INT, IN optional INT, IN optional2 VARCHAR(20))
  BEGIN

    SELECT
      0, '', '', mandatory, optional, optional2
    INTO 
      @params, @sql, @where, @m, @o1, @o2;

    IF (@o1 > '' AND @o1 IS NOT NULL) THEN
      SET @where = CONCAT(@where, ' AND field = ?');
      SET @params = @params + 1;
    END IF;
    IF (@o2 > '' AND @o2 IS NOT NULL) THEN
      SET @where = CONCAT(@where, ' AND field2 = ?');
      SET @params = @params + 3;
    END IF;

    SET @sql = CONCAT('
      SELECT id, bar FROM table
      WHERE
        baz = ?
        ', @where
    );
    PREPARE STMT FROM @sql;
    CASE @params
      WHEN 0 THEN EXECUTE STMT USING @m;
      WHEN 1 THEN EXECUTE STMT USING @m, @o1;
      WHEN 3 THEN EXECUTE STMT USING @m, @o2;
      WHEN 4 THEN EXECUTE STMT USING @m, @o1, @o2;
    END CASE;
    DEALLOCATE PREPARE STMT;

  END$$

私は代替案を知っています:

  • これらのストアドプロシージャを呼び出すバイナリには、ユーザーが指定した文字列を正規表現で渡すことにより、SQLインジェクションの可能性を識別しようとする機能があります。
  • ユーザー定義関数を使用して、動的な数の入力が指定されたEXECUTEステートメントを動的に構築できます。

しかし、SQLだけでEXECUTEステートメントの動的構築を処理したいという思いが他の誰かに出くわしたのではないかと思っていました。

5
Erin Schoonover

少なくともこの例では、これよりも簡単な方法があります。オプティマイザは常に、有効な結果セットを取得するために必要な作業量が最も少ないパスが選択されたパスになるように、クエリの実行を計画しようとしていることに注意してください。

_SELECT * FROM t1 WHERE (col1 = 'foo') OR (1 = 1);
_

サーバーはalwaysすべての行を返します。1= 1には「はい、この行はWHERE句に一致する」と結論付けるために必要なすべての真実性が含まれているためです...オプティマイザがより明確でシンプルな「低コスト」アプローチを見つけたため、col1の値をスキャンして「foo」が含まれているかどうかを確認する必要はありません... _(any expression) OR (TRUE)_は常にtrue ...と真実への最短パスオプティマイザが検索して取得するはずのものです。 _1 = 1_が行ごとかどうかを評価する必要はありません。これは定数式だからです。

準備されたステートメントは必要ありません。プロシージャのクエリだけです。

_SELECT id, bar 
  FROM table
 WHERE baz = mandatory
   AND (optional IS NULL OR field = optional)
   AND (optional2 IS NULL OR field2 = optional2);
_

プログラム変数の「オプション」がnullの場合、オプティマイザは、実行中のクエリのコンテキストでは「オプション」が定数であるため、式全体(その行の括弧内)が各行に対してtrueであることを認識します。値、および_optional is NULL_が常にtrueであるように、_1 = 1_は常にtrueになります。オプティマイザは、これも定数式であることを認識しています。 「フィールド」の内容を評価する必要はありません。これは、OR式が事前にtrueであると判断されているためです。同じことがoptional2にも言えます。

反対に、「optional」がnullでない場合、その式を真にすることは不可能であるため、_optional IS NULL_は最適化され、AND (field = optional)が残ります。同様に、「optional2」についても同様です。

オプティマイザには、いずれの場合も、必要に応じてインデックスを使用できるクエリが残ります。

クエリは準備されたステートメントではなくプロシージャ本体にあるため、サーバーは変数内のデータとリテラルクエリの違いを曖昧にすることができないため、SQLインジェクションは不可能です。

5