WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

パラメータ化の副次的な効用

by WebSurfer 2016年6月4日 15:12

パラメータ化クエリを使用するというセキュリティ対策として普通にやるべきことをやっていれば、照合順序の違いによる文字化けに悩むことはなさそうという話を書きます。

元は MSDN Forum の「nvarcharに日本語を入力すると?????????になる」という表題のスレッドでの話です。

上の MSDN Forum のスレッドの問題は、簡単に書くと、SQL Azure の照合順序のデフォルトは SQL_Latin1_General_CP1_CI_AS となっていて、それに例えば以下のように 'あいうえお' というように N プレフィックスをつけないリテラルを INSERT すると文字化けするという話です。

INSERT INTO [Table] ([Name]) VALUES ('あいうえお')

(何故文字化けするかは MSDN Blog の記事「Unicode型列(NCHAR/NVARCHAR) に格納されるデータが “?” になる」に説明されていますので、そちらを見てください。手抜きでスミマセン)

クエリをパラメータ化して ADO.NET + SqlClient 経由で INSERT, UPDATE をかければ、照合順序が Japanese_CI_AS(デフォルト)でも SQL_Latin1_General_CP1_CI_AS(SQL Azure のデフォルトらしい)でも文字化けはしません。

パラメータ化あり / なしでどう違うかの例を以下に書きます。

まず、サンプルとして SQL Server 2008 Express に照合順序が SQL_Latin1_General_CP1_CI_AS のデータベースを作りました。以下の画像の通りです。

照合順序

それに以下のコードで INSERT してみます。上のクエリがパラメータ化なし、下のクエリがパラメータ化ありです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace ConsoleApplication
{
  class Program
  {
    static void Main(string[] args)
    {
      string connString = @"接続文字列";

      using(SqlConnection conn = new SqlConnection(connString))
      {
        conn.Open();
        string query = 
          "INSERT INTO [Table] ([Name]) VALUES ('あいうえお')";
        using(SqlCommand cmd = new SqlCommand(query, conn))
        {
          cmd.ExecuteNonQuery();
        }

        query ="INSERT INTO [Table] ([Name]) VALUES (@Name)";
        using(SqlCommand cmd = new SqlCommand(query, conn))
        {
          cmd.Parameters.Add(
            new SqlParameter("@Name", SqlDbType.NVarChar, 50));
          cmd.Parameters["@Name"].Value = "かきくけこ";
          cmd.ExecuteNonQuery();
        }
      } 
    }
  }
}

結果は以下の通りです。赤枠で囲ったものがパラメータ化なし、青枠で囲ったものがパラメータ化ありです。

INSERT 結果

何故パラメータ化クエリを使うと文字化けの問題がなくなるのかと言うと、TechNet の記事「第 4 回 アドホック クエリのパラメータ化」の下の方に書いてあるように "SqlParameter クラスを利用すると、内部的には sp_executesql に変換されて実行されるようになり"、 その際以下のように N プレフィックスが付与されるからです。

exec sp_executesql 
    N'INSERT INTO [Table] ([Name]) VALUES (@Name)',
    N'@Name nvarchar(5)',
    @Name=N'かきくけこ'

というわけで、クエリをパラメータ化して ADO.NET + SqlClient を使えば(普通のやり方をしていれば)、照合順序の違いによって文字化けに悩むことはなさそうです。

Tags: ,

ADO.NET

パラメータ化クエリ

by WebSurfer 2012年2月2日 22:47

パラメータ化クエリについて少々(かなり?)誤解してました。どのように誤解していたかは恥ずかしいので秘密です。(笑) 調べたことを備忘録として書いておきます。

パラメータ化クエリの説明図

パラメータ化の説明で、パラメータの入力はリテラルとして扱われるから SQL インジェクション攻撃を防ぐことができると言われています。

そこがキーポイントですが、そのリテラルというのが何だか分らないと話が始まりませんので、まずクエリの要素の名前の説明をします。以下の SELECT クエリを例に取ります。

SELECT id, Product FROM TableA WHERE Category='server'

クエリを構成する要素には、キーワード、演算子、識別子、リテラルなどがあり、上記の SELECT クエリではそれぞれ以下のようになります。

キーワード SELECT FROM WHERE
演算子 =
識別子 id Product Category TableA
リテラル 'server'

クエリをパラメータ化するというのはリテラルの部分をプレースホルダを使って記述することです。上記の SELECT クエリの例では以下のようになります。

SELECT id, Product FROM TableA WHERE Category=@Category

プレースホルダの構文はデータソースに依存します。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 プロバイダの場合は、疑問符 (?) で指定される位置パラメータマーカーが使用されますがその話は割愛します。詳しくは、パラメータおよびパラメータのデータ型の構成 (ADO.NET) の「パラメーターのプレースホルダーの使用」セクションを参照してください)

プレースホルダの方式には静的と動的があって、SQL Server の場合は静的プレースホルダを使用します。静的/動的の違いの説明については安全なSQLの呼び出し方 - IPA 独立行政法人 情報処理推進機構を参照してください。

動作は次のとおりです(上の図も参照ください)。まず、プレースホルダのままのクエリをデータベースエンジン側にあらかじめ送信して、実行前にクエリのコンパイルなどの準備をしておきます。クエリの実行の段階で、Parameters コレクションに追加したパラメータの値をデータベースに送信し、データベースがバインド処理します。

ユーザー入力から直接クエリを組み立ててコマンドテキストとして渡すのとは異なり、パラメータの入力は実行可能なコードとしてではなく、リテラル値として扱われます。これにより、攻撃者がサーバーのセキュリティを侵害するコマンドを "注入" しても、注入した値はリテラルの外にはみ出すことはない(上の SELECT クエリの例で言うと、WHERE 句の条件が true または false になるだけ)ので、SQL インジェクション攻撃を防ぐことができます。

なお、パラメータ化するとエスケープ処理されるという話を時々聞きますが、少なくとも SQL Server の場合はそれは誤解です(エスケープ処置はされません。静的プレースホルダ方式なので処置する必要がありません)。

パラメータ化のもう一つのメリットにパフォーマンスの向上があります。それについては 第 4 回 アドホック クエリのパラメータ化 が参考になると思います。このページには、SqlParameter クラスを利用した場合、SQL Server の内部的にどのようにパラメータ化クエリが実行されるかも書いてあって(sp_executesql に変換されて実行される)、興味深いと思いました。

-------- 2016/6/5 追記 --------

上に述べたように、SQL インジェクションの防止とパフォーマンスの向上がパラメータ化クエリを使う主な目的ですが、それ以外にも照合順序の違いによって文字化けに悩むことがなくなるという副次的な効用もあります。

詳しくは別の記事「パラメータ化の副次的な効用」に書きましたので、興味がありましたら見てください。

Tags:

ADO.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar