Prevention — .NET
SqlParameter, and the Entity Framework distinction that decides everything: FromSqlRaw vs FromSqlInterpolated.
The Rule
Use parameterised SqlCommand with SqlParameter, or Entity Framework's LINQ and interpolated-SQL APIs. Never concatenate or String.Format user input into a command.
The .NET-specific trap is Entity Framework Core's pair of raw-SQL methods: FromSqlInterpolated parameterises, FromSqlRaw does not, and they are one word apart.
ADO.NET
// Concatenation and String.Format are both injectable.
var cmd = new SqlCommand(
"SELECT * FROM Users WHERE Name = '" + name + "'", conn);
var cmd2 = new SqlCommand(
string.Format("SELECT * FROM Users WHERE Id = {0}", id), conn);Entity Framework Core
// FromSqlRaw with an interpolated or concatenated string does NOT
// parameterise. The name is the warning.
var users = context.Users
.FromSqlRaw($"SELECT * FROM Users WHERE Name = '{name}'")
.ToList();
// ExecuteSqlRaw has the same trap.
context.Database.ExecuteSqlRaw(
"DELETE FROM Users WHERE Name = '" + name + "'");Dapper
// Dapper does not parameterise a string you built yourself.
var users = conn.Query<User>(
"SELECT * FROM Users WHERE Name = '" + name + "'");Dynamic Identifiers
No parameter binds a column or table name. Allowlist:
private static readonly Dictionary<string,string> Sortable = new()
{
["name"] = "Name", ["date"] = "CreatedAt", ["price"] = "Price"
};
var column = Sortable.GetValueOrDefault(userSort, "Id");
var dir = userDir == "desc" ? "DESC" : "ASC";
var sql = $"SELECT * FROM Products ORDER BY {column} {dir}";
// column and dir are values you control, not user text.
Checklist
SqlParameterfor every value; never concatenate orString.Format.- EF Core:
FromSqlInterpolated/ExecuteSqlInterpolated, orFromSqlRawwith explicit parameters. Ban bareFromSqlRawwith interpolation in review. - Dapper: anonymous parameter objects.
- Identifiers: allowlist.
- Turn on the analyzer warnings (
CA2100, the EF Core raw-SQL analyzers) and treat them as errors. - Set ASP.NET
customErrors/UseExceptionHandlerso database errors do not reach clients — see Error-Based. - Connect with a least-privilege SQL login, never
sa— see MSSQL.
Related
Every ORM has an escape hatch to raw SQL, and every one of them is a footgun. Where the abstraction stops protecting you, and which method names to grep for.
Verbose errors, universal stacked-query support, and xp_cmdshell — the friendliest engine to attack and the one where escalation is most direct.
Where your input lands in the query determines which payloads can possibly work. Get the context wrong and every payload fails for the wrong reason.
Coercing the database into embedding your query result inside its own error message. Fast extraction when errors reach the client but results do not.
Parameterisation removes the bug; these layers limit the blast radius when a parameterisation is missed. Least privilege, allowlists, monitoring, and what each is actually worth.