SQL Server でのパラメータ化クエリについて少々(かなり?)誤解してました。どのように誤解していたかは恥ずかしいので秘密です。(笑) 調べたことを備忘録として書いておきます。
パラメータ化の説明で、パラメータの入力はリテラルとして扱われるから SQL インジェクション攻撃を防ぐことができると言われています。
そこがキーポイントですが、そのリテラルというのが何だか分らないと話が始まりませんので、まずクエリの要素の名前の説明をします。以下の SELECT クエリを例に取ります。
SELECT id, Product FROM TableA WHERE Category='server'
クエリを構成する要素には、キーワード、演算子、識別子、リテラルなどがあり、上記の SELECT クエリではそれぞれ以下のようになります。
要素 | 上のクエリで該当するもの |
---|---|
キーワード | SELECT FROM WHERE |
演算子 | = |
識別子 | id Product Category TableA |
リテラル | 'server' |
プレースホルダの構文はデータソースに依存します。SQL Server の場合は @parametername
形式の名前付きパラメータが使用されます。(頭に @ を付けるのは単なる命名規則です)
パラメータ名は必ずしも識別子と同じ名前にする必要はありませんが、同じにしておいた方が混乱がなさそうです。(例えば、識別子の名前が Category ならパラメータ名は @Category
とする)
ADO.NET のコマンドオブジェクトは、パラメータを使用して SQL ステートメントまたはストアドプロシージャに値を渡すことを可能にしています。SQL Server の場合で、上記 SELECT クエリを例に取ると以下のようになります。
string connectionString =
WebConfigurationManager.
ConnectionStrings["MyDB"].ConnectionString;
string query =
"SELECT id, Product FROM TableA WHERE Category=@Category";
using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlCommand command =
new SqlCommand(query, connection);
SqlParameter param =
new SqlParameter("@Category", SqlDbType.VarChar, 50);
command.Parameters.Add(param);
command.Parameters["@Category"].Value = TextBox1.Text;
connection.Open();
SqlDataReader reader = command.ExecuteReader();
// 中略
}
SQL Server の場合、Parameters コレクションに追加したパラメータの名前は、クエリのパラメータ名前と一致している必要があります。(Access 等に使われる OleDb プロバイダの場合は、疑問符 (?) で指定される位置パラメータマーカーが使用されますがその話は割愛します。詳しくは、Microsoft のドキュメント「パラメータおよびパラメータのデータ型の構成 (ADO.NET)」の「パラメーターのプレースホルダーの使用」セクションを参照してください)
プレースホルダの方式には静的と動的があって、SQL Server の場合は静的プレースホルダを使用します。静的/動的の違いの説明については「安全なSQLの呼び出し方 - IPA 独立行政法人 情報処理推進機構」を参照してください。
動作は次のとおりです(上の図も参照ください)。まず、プレースホルダのままのクエリをデータベースエンジン側にあらかじめ送信して、実行前にクエリのコンパイルなどの準備をしておきます。クエリの実行の段階で、Parameters コレクションに追加したパラメータの値をデータベースに送信し、データベースがバインド処理します。
ユーザー入力から直接クエリを組み立ててコマンドテキストとして渡すのとは異なり、パラメータの入力は実行可能なコードとしてではなく、リテラル値として扱われます。これにより、攻撃者がサーバーのセキュリティを侵害するコマンドを "注入" しても、注入した値はリテラルの外にはみ出すことはないので(上の SELECT クエリの例で言うと、WHERE 句の条件が true または false になるだけなので)ので、SQL インジェクション攻撃を防ぐことができます。
なお、パラメータ化するとエスケープ処理されるという話を時々聞きますが、少なくとも SQL Server の場合はそれは誤解です(エスケープ処置はされません。静的プレースホルダ方式なので処置する必要がありません)。
パラメータ化のもう一つのメリットにパフォーマンスの向上があります。それについては @IT
の記事「キャッシュを無駄遣いしないようにクエリを書く」が参考になると思います。そこに "sp_executesqlというストアドプロシージャを使うようにしてください" と書いてありますが、SqlParameter クラスを利用した場合、SQL Server の内部的にパラメータ化クエリが sp_executesql に変換されて実行されます。
上に述べたように、SQL インジェクションの防止とパフォーマンスの向上がパラメータ化クエリを使う主な目的ですが、それ以外にも照合順序の違いによって文字化けに悩むことがなくなるという副次的な効用もあります。それについては別の記事で書く予定です。