はじめに 「SQLインジェクション対策」は“文字列をいじる話ではなく、SQLの組み立て方の話”
「文字列処理のユーティリティ」と聞くと、
「シングルクォートを2つに置き換えるエスケープ関数を作ればいいのかな?」
と考えがちですが、SQLインジェクション対策は本質的には 文字列エスケープの話ではありません。
本質はただ一つです。
SQL文と値(パラメータ)を、絶対に文字列連結で混ぜないこと。
C# では「パラメータ化クエリ」を使うことで、
ユーザー入力を安全にSQLに渡すことができます。
ここでは、初心者向けに、
SQLインジェクションがなぜ起きるのか
やってはいけない「文字列連結」と「自前エスケープ」
正しい「パラメータ化」の書き方と、ユーティリティ化の考え方
LIKE検索などで“だけ”文字列処理が必要になる場面
を、例題付きでかみ砕いて説明していきます。
なぜSQLインジェクションが起きるのか
文字列連結でSQLを組み立てると何が起きるか
まず、典型的な「危ないコード」を見てみます。
string userName = GetUserInput(); // ユーザー入力
string sql = "SELECT * FROM Users WHERE Name = '" + userName + "'";
using var connection = new SqlConnection(connectionString);
using var command = new SqlCommand(sql, connection);
connection.Open();
using var reader = command.ExecuteReader();
C#一見すると、「Name = ‘入力された名前’」という条件を作っているだけに見えます。
しかし、ユーザーが次のような文字列を入力したらどうなるでしょうか。
' OR 1=1 --
すると、SQLはこうなります。
SELECT * FROM Users WHERE Name = '' OR 1=1 --'
OR 1=1 によって条件が常に真になり、-- 以降はコメント扱いになってしまいます。
結果として、「全ユーザーが取得される」など、
開発者の意図しないSQLが実行されてしまいます。
これが SQLインジェクションです。
原則:文字列エスケープではなく「パラメータ化」
良くない「SQLエスケープユーティリティ」の発想
よくある誤解として、
「シングルクォートを '' に置き換えるユーティリティを作れば安全なのでは?」
という発想があります。
例えば、こんな感じです。
public static string EscapeSqlLiteral(string value)
{
return value.Replace("'", "''");
}
C#そして、
string userName = EscapeSqlLiteral(GetUserInput());
string sql = $"SELECT * FROM Users WHERE Name = '{userName}'";
C#一見マシになったように見えますが、
これは「DBごとの仕様」「文字コード」「照合順序」「他の特殊文字」などを考え始めると、
途端に破綻しやすいアプローチです。
何より、「SQL文と値を文字列として混ぜている」という構造自体が危険です。
正しい発想:「SQL」と「値」を分ける
C# での正しい対策は、パラメータ化クエリを使うことです。
SQL文の中には「プレースホルダ(@Name など)」だけを書き、
実際の値は SqlParameter として別に渡します。
string userName = GetUserInput();
string sql = "SELECT * FROM Users WHERE Name = @Name";
using var connection = new SqlConnection(connectionString);
using var command = new SqlCommand(sql, connection);
command.Parameters.Add(new SqlParameter("@Name", SqlDbType.NVarChar) { Value = userName });
connection.Open();
using var reader = command.ExecuteReader();
C#ここで重要なのは、
SQL文は常に固定の文字列
ユーザー入力は「パラメータ」として別枠で渡す
という構造になっていることです。
DBドライバが「値を適切にエスケープして安全に渡す」処理をやってくれるので、
自分で文字列をいじる必要はありません。
ADO.NETでのパラメータ化クエリをユーティリティ化する
SqlCommand にパラメータを追加する基本形
パラメータ追加の基本形は次の通りです。
var command = new SqlCommand(sql, connection);
command.Parameters.Add("@Name", SqlDbType.NVarChar).Value = userName;
command.Parameters.Add("@Age", SqlDbType.Int).Value = age;
C#これを毎回手で書いてもいいのですが、
業務システムでは「似たようなコード」が大量に出てきます。
そこで、「パラメータ追加を少しだけ楽にするユーティリティ」を用意すると便利です。
シンプルなパラメータ追加ユーティリティ
using System.Data;
using Microsoft.Data.SqlClient; // または System.Data.SqlClient
public static class SqlCommandExtensions
{
public static SqlParameter AddParam(this SqlCommand command, string name, SqlDbType type, object? value)
{
var param = command.Parameters.Add(name, type);
param.Value = value ?? DBNull.Value;
return param;
}
}
C#使い方はこうなります。
string sql = "SELECT * FROM Users WHERE Name = @Name AND Age >= @Age";
using var connection = new SqlConnection(connectionString);
using var command = new SqlCommand(sql, connection);
command.AddParam("@Name", SqlDbType.NVarChar, userName);
command.AddParam("@Age", SqlDbType.Int, minAge);
connection.Open();
using var reader = command.ExecuteReader();
C#ここでのポイントは、
パラメータ追加を1行で書ける
null のときに DBNull.Value を自動で入れてくれる
といった「ちょっとした楽さ」です。
重要なのは、“文字列をエスケープするユーティリティ”ではなく、“パラメータを追加するユーティリティ”を作ることです。
LIKE検索やワイルドカードでだけ、文字列処理が必要になる
LIKE検索の基本形
名前の部分一致検索などで、次のようなSQLを書きたくなることがあります。
SELECT * FROM Users WHERE Name LIKE '%山田%'
これをパラメータ化すると、こうなります。
string keyword = GetUserInput(); // 例: "山田"
string sql = "SELECT * FROM Users WHERE Name LIKE @Pattern";
using var connection = new SqlConnection(connectionString);
using var command = new SqlCommand(sql, connection);
string pattern = "%" + keyword + "%";
command.Parameters.Add("@Pattern", SqlDbType.NVarChar).Value = pattern;
C#ここで初めて、「文字列連結」が出てきます。
ただし、連結しているのは SQL文ではなく、パラメータの値 です。
SQL文自体は "WHERE Name LIKE @Pattern" のまま固定なので、
インジェクションの危険はありません。
ワイルドカード文字(% と _)をエスケープしたい場合
ユーザー入力に % や _ が含まれていると、
LIKE のワイルドカードとして解釈されてしまいます。
「入力された文字列を“そのまま”検索したい」場合は、% と _ をエスケープする必要があります。
例えば、SQL Server なら ESCAPE 句と組み合わせて、
次のようなユーティリティを作れます。
public static class SqlLikeUtil
{
public static string EscapeLikePattern(string value, char escapeChar = '\\')
{
if (string.IsNullOrEmpty(value))
{
return value;
}
string s = value
.Replace(escapeChar.ToString(), escapeChar.ToString() + escapeChar)
.Replace("%", escapeChar + "%")
.Replace("_", escapeChar + "_");
return s;
}
}
C#使い方はこうです。
string keyword = GetUserInput(); // 例: "100%達成"
string escaped = SqlLikeUtil.EscapeLikePattern(keyword);
string pattern = "%" + escaped + "%";
string sql = "SELECT * FROM Goals WHERE Title LIKE @Pattern ESCAPE '\\'";
using var command = new SqlCommand(sql, connection);
command.Parameters.Add("@Pattern", SqlDbType.NVarChar).Value = pattern;
C#ここでの文字列処理は、あくまで「LIKE のワイルドカード制御」のためであり、
SQLインジェクション対策の本体ではありません。
本体はあくまで「パラメータ化」です。
ORマッパー(Dapper / EF Core)を使う場合の考え方
Dapper の例
Dapper のようなマイクロORMを使うと、
パラメータ化はさらに簡単になります。
string sql = "SELECT * FROM Users WHERE Name = @Name";
var users = connection.Query<User>(sql, new { Name = userName });
C#ここでも、@Name はパラメータとして扱われ、
Dapper が内部で安全に値をバインドしてくれます。
自分で「SQLエスケープユーティリティ」を書く必要はありません。
EF Core の例
Entity Framework Core では、
LINQ を書くだけで内部的にパラメータ化されたSQLが生成されます。
var users = dbContext.Users
.Where(u => u.Name == userName)
.ToList();
C#このとき、生成されるSQLはだいたい次のような形です。
SELECT ... FROM Users WHERE Name = @__userName_0
ここでも、@__userName_0 はパラメータです。
LINQ を素直に書いている限り、SQLインジェクションの心配はほぼありません。
まとめ 「SQLインジェクション対策ユーティリティ」は“文字列をいじる道具”ではなく“パラメータを徹底させる仕組み”
SQLインジェクション対策は、「危険な文字をエスケープする」話ではなく、
「SQLと値を絶対に文字列連結しない」ための設計と習慣の話です。
押さえておきたいポイントは、
SQL文にユーザー入力を + や $"{...}" で埋め込まない
必ず @Name のようなプレースホルダと SqlParameter(またはORMのパラメータ)を使う
ユーティリティを作るなら「エスケープ関数」ではなく「パラメータ追加ヘルパー」にする
LIKE検索などでだけ、パラメータの“中身”を文字列処理する(SQL文は固定のまま)
ORM(Dapper / EF Core)を使う場合も、「パラメータ化されている構文」を選ぶ
ここまで理解できれば、「なんとなく危なそうだからエスケープしている」段階から一歩進んで、
“SQLインジェクションを構造的に防ぐC#ユーティリティとコーディングスタイル”を、自分の中にしっかり根付かせていけるようになります。
