flawopen.com/C#のSQL Injection/EF Core FromSqlRaw
Entity Framework CoreのFromSqlRawは安全ですか?
値がSQLテキストと分離されていれば安全です。FromSqlRawは生SQLを受け取るため、信頼できない入力を連結または補間するとクエリが変わるおそれがあります。値は個別のパラメーター引数で渡すか、補間で生成したFormattableStringをFromSqlInterpolated(EF Core 7以降ではFromSqlも可)に渡します。パラメーターは封印された値の枠のようなものです。データベースに届くのは値であり、新しいSQL命令ではありません。
// 補間は先に文字列として評価されます。 var sql = $"SELECT * FROM Users WHERE Id = {id}"; // FromSqlRawが受け取るのはSQLテキストで、FormattableStringではありません。 var query = context.Users.FromSqlRaw(sql);
// EF Core 3–6:位置プレースホルダーと別引数の値。 var byRawParameter = context.Users.FromSqlRaw( "SELECT * FROM Users WHERE Id = {0}", id); // EF Core 3以降:補間文字列をパラメーターAPIに渡します。 var byInterpolation = context.Users.FromSqlInterpolated( $"SELECT * FROM Users WHERE Id = {id}"); // EF Core 7以降:FromSqlもFormattableStringを受け取ります。 var byFromSql = context.Users.FromSql( $"SELECT * FROM Users WHERE Id = {id}");
見落としやすいポイント
FromSqlInterpolatedはFormattableStringを受け取ります。EF Coreは書式と引数を読み取り、補間された各値を個別のデータベースパラメーターとして送ります。コンパイル時に補間を横取りするわけではありません。EF Core 7では同じ安全な動作をするFromSqlが追加されました。それ以前のバージョンではFromSqlInterpolatedを使います。FromSqlRawも、{0}のようなプレースホルダーに対応する値を別引数で渡せば安全です。補間文字列を先にstringへ代入すると、パラメーターのメタデータは失われます。
パラメーターが束縛するのは値であり、列名ではありません
データベースパラメーターで指定できるのは値であり、テーブル名、列名、SQL演算子、並び順ではありません。クエリ構造を切り替える場合は、アプリケーションコード内の固定許可リストから識別子を選びます。識別子をパラメーター化したり、文字フィルターを許可リストの代わりにしたりしないでください。認可とクエリコストも確認します。有効な列でも機密情報を公開したり、高コストなスキャンを起こしたりする可能性があります。
- ✓値には直接補間するFromSqlInterpolated/FromSql、または個別のパラメーターを使うFromSqlRawを使用します。
- ✓連結または補間したユーザー入力をFromSqlRawに渡さないでください。FormattableStringを先にstringへ変換しないでください。
- ✓動的識別子は固定許可リストからのみ選びます。対象フィールドへの認可と適切なインデックスを個別に確認します。
- ✓クエリを明確に表現できる場合はLINQを優先します。パラメーター化はSQL構文インジェクションを防ぎますが、認可や業務ルールは適用しません。
コードベースの確認方法
rg -n -g '*.cs' '(FromSqlRaw|FromSqlInterpolated|FromSql)\s*\(' .よくある質問
SQLにプレースホルダーがあり、値を別引数として渡す場合は安全です。例:FromSqlRaw("... {0}", value)。値を先にSQL文字列へ連結・補間しないでください。
FormattableStringを受け取り、書式と値を分けて保持するためです。EF Coreは各値をデータベースパラメーターに変換します。先にstringへ代入したりFromSqlRawに渡したりすると保護が失われます。
いいえ。パラメーターは値だけを表します。動的識別子はユーザーの選択を既知の識別子の固定許可リストに対応付け、認可とクエリコストを確認してください。