flawopen.com/SQL Injection/C#

SQL Injection in C#

Critical CWE-89 Draft — pending review
ELI5

Imagine a form that only ever expects a ticket number, like 482. SQL injection is what happens when someone types something sneaky into that box instead of a number — a trick phrase that makes the system say "show me every ticket" instead of just ticket 482, because the system never checked that what it received was actually just a number.

Key terms on this page
user-controlled input
Any value that ultimately came from whoever is using — or attacking — the app: a form field, a URL parameter, an uploaded filename, an HTTP header. The app can't assume it's well-formed or safe.
SQL query
The command sent to a database — e.g. "get this row," "delete this table." Its meaning comes entirely from its exact text, which is what makes injecting extra text into it dangerous.

What's happening

SQL injection happens when user-controlled input gets inserted directly into a database query's text. In C#, string interpolation ($"...") reads exactly like ordinary, idiomatic string building — which is what makes it easy to accidentally use for a SqlCommand's query text.

Real-world impact

In 2015, UK telecom TalkTalk suffered a breach affecting over 150,000 customers after attackers exploited a SQL injection flaw in a legacy web page inherited through a company acquisition. The UK's data protection regulator fined TalkTalk £400,000, describing the failure as preventable and basic.

Source: UK Information Commissioner's Office enforcement notice, 2016 — see References below.

Vulnerable vs. fixed

VULNERABLE
// userId comes straight from the request
var query =
  $"SELECT * FROM Users WHERE Id = {userId}";
var cmd = new SqlCommand(query, connection);
var reader = cmd.ExecuteReader();
FIXED
// value bound as a parameter
var cmd = new SqlCommand(
  "SELECT * FROM Users WHERE Id = @id",
  connection);
cmd.Parameters.AddWithValue("@id", userId);
var reader = cmd.ExecuteReader();

Why the fix works

SqlCommand.Parameters sends the value to SQL Server separately from the compiled query text, so the value can never alter the query's structure. A $"..." interpolated string can't provide this — the value is already baked into the string before SqlCommand ever sees it.

C#-specific gotchas

String interpolation looks identical whether it's safe or not

$"WHERE Id = {userId}" and a hardcoded $"WHERE Id = 42" are visually indistinguishable at a glance — the danger is purely in what fills the interpolation, which is easy to miss in review.

Entity Framework's FromSqlRaw vs FromSqlInterpolated

EF Core's normal LINQ queries parameterize automatically. FromSqlRaw() built with string concatenation does not. FromSqlInterpolated() is a genuinely EF-specific safety feature — it accepts an interpolated string but auto-parameterizes each interpolated value instead of inlining it, so the safe pattern in EF looks almost identical to the unsafe one in plain ADO.NET.

Dapper is safe with named parameters, not string-built SQL

connection.Query<User>("... WHERE Id = @Id", new { Id = userId }) is safe. Building the SQL string first and passing no parameters object is not, regardless of how the string was assembled.

Common misconceptions

"Entity Framework means I never write raw SQL"

FromSqlRaw exists precisely for cases needing raw SQL, and it's opt-in unsafe unless parameterized — EF doesn't prevent you from writing it.

"AddWithValue infers the type, so it's just for convenience"

Type inference is a real (separate) footgun with AddWithValue for performance reasons, but the parameter-binding safety property holds regardless — don't confuse the type-inference caveat with the injection protection, which is solid either way.

How to check if you're affected

grep -rn 'new SqlCommand($"' --include="*.cs" . grep -rn "FromSqlRaw(" --include="*.cs" . | grep '+\|\$"'
Roslyn security analyzers (e.g. Microsoft.CodeAnalysis.NetAnalyzers rule CA2100) flag SQL built from unvalidated input directly in the IDE and in CI.

Prevention checklist

FAQ

Is FromSqlInterpolated actually safe, given it takes an interpolated string?

Yes — EF Core intercepts the interpolation at compile time and converts each interpolated value into a real bound parameter before the query runs. This is different from a plain C# interpolated string passed to SqlCommand, which stays a flat string.

Does Dapper protect me automatically?

Only when you pass a parameters object alongside a parameterized query string — building the SQL text yourself and passing no parameters object bypasses that entirely.

References

View in: Python JavaScript Go Java PHP C# Ruby C/C++ Rust Kotlin Swift Solidity (N/A)
Also see: Command InjectionPath Traversal XSSInsecure Deserialization