Webセキュリティの小部屋

Twitter のフォローはこちらから Facebook ページはこちら RSSフィードのご登録はこちらから
公開日:2013年12月15日
最終更新日:2020年8月13日

Like 句に対する SQL インジェクション対策(ASP.NET,C#, VB.NET 編)

概要

SQL インジェクションは、プレースホルダを利用することで対策が可能ですが、Like 句のワイルドカードついては、ほとんどの情報元が「別途エスケープが必要です」としか記載されていません。

この記事では、SQL Server に対するワイルドカードのエスケープ方法の、C# と VB.NET の具体的例を紹介します。動作確認環境は、.NET Framework 4.5 + SQL Server 2012 ですが、最近の環境なら問題なく動作すると思います。

また、SQL インジェクションの対策としては、LINQ が推奨されますが、理解しやすいように、先に DataReader のサンプルを紹介後、LINQ のサンプルを紹介します。

サンプルアプリケーションの概要は、以下の通りです。

search

SQL Server のワイルドカードのエスケープ

SQL Server のワイルドカードは、以下のようになります。対象となる SQL Server は現時点で、SQL Server 2005 ~ 2012 までです。

%0個以上の文字
_1文字
[][]内に指定した任意の文字

LIKE (Transact-SQL)

SQL Server でワイルドカードをエスケープするには、エスケープ対象となるワイルドカード文字(% _ [) を [] で囲むことです。こうすることで、% _ [ を文字列として検索することができるようになります。

なお、以下のような ESCAPE 句を使用することにより、#% #_ #[ と簡単にエスケープできるようになります。

WHERE field LIKE @Parameter ESCAPE '#'

DataReader を使用した例

・Search.aspx (C#)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.SqlClient;
using System.Text.RegularExpressions;

namespace WebAppCS
{
  public partial class Search : System.Web.UI.Page
  {
    protected void buttonSearch_Click(object sender, EventArgs e)
    {
      //検索結果格納用
      Dictionary<string, string> result = new Dictionary<string, string>();

      //接続文字列
      string connectionString = @"Server=(local)\SQLEXPRESS;Initial Catalog=Test;User ID=user01;Password=pass";

      //接続作成
      SqlConnection con = new SqlConnection(connectionString);

      //プレースホルダで SQL 作成
      string sql = "SELECT ID,NAME FROM USERS WHERE NAME LIKE @NAME ESCAPE '#';";

      //パラメーター取得・エスケープ
      string name = escapeString(this.TextBoxName.Text);
      this.TextBoxName.Text = name;

      //プレースホルダにパラメーター設定
      SqlCommand command = new SqlCommand(sql, con);
      SqlParameter pName = new SqlParameter("@NAME", name);
      command.Parameters.Add(pName);

      //接続オープン
      con.Open();

      //SQL 実行
      SqlDataReader reader = command.ExecuteReader();

      if (reader.HasRows)
      {
        while (reader.Read())
        {
          result.Add((string)reader["ID"], (string)reader["NAME"]);
        }
      }

      reader.Close();
      con.Close();

      if (result.Count > 0)
      {
        Session["result"] = result;
        Response.Redirect("Result.aspx");
      }
     
    }

    private string escapeString(string name)
    {
      //ワイルドカードをエスケープ
      return "%" + Regex.Replace(name, "[_%\\[#]", "#$0") + "%";
    }
  }
}

・Search.aspx (VB.NET)

Imports System.Data.SqlClient

Public Class Search
  Inherits System.Web.UI.Page

  Protected Sub ButtonSearch_Click(sender As Object, e As EventArgs) Handles ButtonSearch.Click
    '検索結果格納用'
    Dim result As New Dictionary(Of String, String)

    '接続文字列'
    Dim connectionString As String = "Server=(local)\SQLEXPRESS;Initial Catalog=Test;User ID=user01;Password=pass"

    '接続作成'
    Dim con As SqlConnection = New SqlConnection(connectionString)

    'プレースホルダで SQL 作成'
    Dim sql As String = "SELECT ID,NAME FROM USERS WHERE NAME LIKE @NAME ESCAPE '#';"

    'パラメーター取得・エスケープ'
    Dim name As String = escapeString(Me.TextBoxName.Text)

    'プレースホルダにパラメーター設定'
    Dim command As SqlCommand = New SqlCommand(sql, con)
    Dim pName As SqlParameter = New SqlParameter("@NAME", name)
    command.Parameters.Add(pName)

    '接続オープン'
    con.Open()

    'SQL 実行'
    Dim reader As SqlDataReader = command.ExecuteReader()

    If reader.HasRows Then
      While reader.Read()
        result.Add(CType(reader("ID"), String), CType(reader("NAME"), String))
      End While
    End If

    reader.Close()
    con.Close()

    If result.Count > 0 Then
      Session("result") = result
      Response.Redirect("Result.aspx")
    End If
  End Sub

  Private Function escapeString(name As String)
    'ワイルドカードをエスケープ'
    Return "%" & Regex.Replace(name, "[_%\[#]", "#$0") & "%"
  End Function

End Class

・Result.aspx (C#)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;

namespace WebAppCS
{
  public partial class Result : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      StringBuilder buf = new StringBuilder();
      Dictionary<string, string> result = 
                 (Dictionary<string, string>)Session["result"];

      foreach (KeyValuePair<string,string> person in result)
      {
        buf.Append(person.Key + ":" + person.Value + ", ");
      }

      this.LabelResult.Text = HttpUtility.HtmlEncode(buf.ToString());
    }
  }
}

・Result.aspx (VB.NET)

Public Class Result
  Inherits System.Web.UI.Page

  Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Dim buf As StringBuilder = New StringBuilder()
    Dim result As Dictionary(Of String, String) = CType(Session("result"), Dictionary(Of String, String))

    For Each person As KeyValuePair(Of String, String) In result
      buf.Append(person.Key & ":" & person.Value + ",")
    Next

    Me.LabelResult.Text = HttpUtility.HtmlEncode(buf.ToString())
  End Sub

End Class

LINQ を使用した例

LINQ では LIKE 句ではなく Contains メソッドを使用することにより、LIKE 句と同じ検索を行うことができます。また、ワイルドカードも自動的にエスケープ処理されます。

・Search.aspx (C#)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.SqlClient;
using System.Text.RegularExpressions;

namespace WebAppCS
{
  public partial class Search : System.Web.UI.Page
  {
    protected void buttonSearch_Click(object sender, EventArgs e)
    {
      //検索結果格納用
      Dictionary<string, string> result = new Dictionary<string, string>();

      //パラメーター取得
      string name = this.TextBoxName.Text;

      //インスタンス作成
      var entities = new UsersEntities();

      //クエリ作成
      var query = from t in entities.USERS
            where t.NAME.Contains(name)
            select t;

      if (query.Count() > 0)
      {
        foreach (var q in query)
        {
          result.Add(q.ID, q.NAME);  
        }

        Session["result"] = result;
        Response.Redirect("Result.aspx");
      }
  }
}

・Search.aspx (VB.NET)

Imports System.Data.SqlClient

Public Class Search
  Inherits System.Web.UI.Page

  Protected Sub ButtonSearch_Click(sender As Object, e As EventArgs) Handles ButtonSearch.Click
    '検索結果格納用'
    Dim result As New Dictionary(Of String, String)

    'パラメーター取得'
    Dim name As String = Me.TextBoxName.Text

    'インスタンス作成'
    Dim entities = New UsersEntities()

    'クエリ作成'
    Dim query = From t In entities.USERS _
          Where t.NAME.Contains(name) _
          Select t

    If query.Count > 0 Then
      For Each q In query
        result.Add(CType(q.ID, String), CType(q.NAME, String))
      Next

      Session("result") = result
      Response.Redirect("Result.aspx")
    End If

  End Sub

End Class

・Result.aspx (C#)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;

namespace WebAppCS
{
  public partial class Result : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      StringBuilder buf = new StringBuilder();
      Dictionary<string, string> result = 
                 (Dictionary<string, string>)Session["result"];

      foreach (KeyValuePair<string,string> person in result)
      {
        buf.Append(person.Key + ":" + person.Value + ", ");
      }

      this.LabelResult.Text = HttpUtility.HtmlEncode(buf.ToString());
    }
  }
}

・Result.aspx (VB.NET)

Public Class Result
  Inherits System.Web.UI.Page

  Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Dim buf As StringBuilder = New StringBuilder()
    Dim result As Dictionary(Of String, String) = CType(Session("result"), Dictionary(Of String, String))

    For Each person As KeyValuePair(Of String, String) In result
      buf.Append(person.Key & ":" & person.Value + ",")
    Next

    Me.LabelResult.Text = HttpUtility.HtmlEncode(buf.ToString())
  End Sub

End Class

参考

Webアプリケーションセキュリティに関する記事は、以下のページにまとまっています。ぜひご確認ください。


スポンサーリンク





カテゴリー:Webアプリケーションセキュリティ対策

コメントを残す

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

CAPTCHA