概要
SQL インジェクションは、プレースホルダを利用することで対策が可能ですが、Like 句のワイルドカードついては、ほとんどの情報元が「別途エスケープが必要です」としか記載されていません。
この記事では、SQL Server に対するワイルドカードのエスケープ方法の、C# と VB.NET の具体的例を紹介します。動作確認環境は、.NET Framework 4.5 + SQL Server 2012 ですが、最近の環境なら問題なく動作すると思います。
また、SQL インジェクションの対策としては、LINQ が推奨されますが、理解しやすいように、先に DataReader のサンプルを紹介後、LINQ のサンプルを紹介します。
サンプルアプリケーションの概要は、以下の通りです。
SQL Server のワイルドカードのエスケープ
SQL Server のワイルドカードは、以下のようになります。対象となる SQL Server は現時点で、SQL Server 2005 ~ 2012 までです。
% | 0個以上の文字 |
_ | 1文字 |
[] | []内に指定した任意の文字 |
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アプリケーションセキュリティに関する記事は、以下のページにまとまっています。ぜひご確認ください。
コメント