Entity Framework and LINQ get marketed as an automatic fix for SQL injection, and for the most part that reputation is earned — parameterized queries generated by EF Core's LINQ provider are not vulnerable to the classic ' OR 1=1-- attack. But "mostly safe by default" is not the same as "safe," and the gap between the two is exactly where injection vulnerabilities still show up in production .NET codebases. FromSqlRaw, ExecuteSqlRaw, string-interpolated LINQ predicates, and stored procedure wrappers all reintroduce raw SQL construction, and each one is a place a developer under deadline pressure can concatenate user input instead of parameterizing it. In 2023, a scan of open-source .NET repositories using EF Core found raw-SQL interpolation vulnerabilities in roughly 1 in 12 projects that used FromSqlRaw at all. This post walks through where those gaps actually appear and how to close them.
Does Entity Framework Actually Prevent SQL Injection?
Yes, but only for the LINQ query surface — not for every API EF exposes. When you write context.Users.Where(u => u.Email == email), EF Core's query translator converts email into a DbParameter before the command ever reaches SQL Server, PostgreSQL, or whatever provider you're using. That parameterization happens at the ADO.NET layer, so the value is sent separately from the SQL text and can never be interpreted as executable syntax, no matter what characters it contains. This has been true since EF6 and remains true in EF Core 8 and 9. The protection covers Where, Select, OrderBy, Include, and the rest of the standard LINQ method syntax. It does not cover raw SQL APIs, which EF Core still exposes deliberately for cases where LINQ can't express the query you need — and that's where the risk concentrates.
Which EF Core APIs Are Still Vulnerable to Injection?
The three highest-risk APIs are FromSqlRaw, ExecuteSqlRaw, and FromSqlInterpolated/ExecuteSqlInterpolated when misused with manual string building instead of the interpolation feature they were built for. FromSqlRaw(string sql, params object[] parameters) takes a raw SQL string and a separate parameter array — used correctly, it's safe, because the parameters are bound positionally. The vulnerability appears when a developer builds the sql argument itself with string concatenation or $"..." interpolation before passing it in:
// Vulnerable: user input concatenated into the SQL string
var name = Request.Query["name"];
var users = context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Name = '" + name + "'")
.ToList();
Compare that to the safe pattern using the same method:
// Safe: parameter placeholder, value passed separately
var users = context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Name = {0}", name)
.ToList();
ExecuteSqlRaw, used for non-query commands like bulk updates, has the identical failure mode. Microsoft's own EF Core documentation flags this explicitly, and CWE-89 (SQL Injection) remains the classification used when these patterns are reported. FromSqlInterpolated looks like it invites the same mistake because it accepts a FormattableString, but EF Core actually decomposes the interpolated expression's placeholders into parameters automatically — so context.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE Name = {name}") is safe. The confusion between "string interpolation" and "SQL string interpolation as a language feature" is a frequent source of mistakes on both sides.
How Common Is SQL Injection in .NET Applications Today?
SQL injection is still one of the most reported vulnerability classes in .NET applications despite ORMs being the default data-access approach for over a decade. It has held a position in the OWASP Top 10's Injection category (A03:2021) continuously since the list's inception, and CWE-89 consistently ranks in the top 5 of MITRE's annual CWE Top 25 Most Dangerous Software Weaknesses list, placing #7 in the 2023 edition with over 5,600 associated CVE records analyzed that year. Legacy ADO.NET code using SqlCommand with concatenated strings still exists inside otherwise-modern EF Core applications — often in performance-tuned reporting queries, admin tooling, or third-party integration layers that predate the migration to EF. A 2022 Veracode State of Software Security report found injection flaws present in 26% of applications scanned, and .NET applications were not meaningfully better than the overall average. The pattern in real incidents is rarely "the ORM failed" — it's that raw SQL was reintroduced at the edges and nobody flagged it in review.
What Does a Real EF Core Injection Vulnerability Look Like?
A realistic example is a search or filter endpoint where a developer needs dynamic sort-column selection, which LINQ handles awkwardly, and reaches for raw SQL to solve it:
// Vulnerable: column name built from user input, no way to parameterize an identifier
public List<Order> GetOrders(string sortColumn)
{
var sql = $"SELECT * FROM Orders ORDER BY {sortColumn}";
return context.Orders.FromSqlRaw(sql).ToList();
}
This is a genuinely hard case because SQL parameters can bind values, not identifiers — you cannot parameterize a column name the way you parameterize a WHERE clause value. The fix is a strict allowlist:
private static readonly HashSet<string> AllowedSortColumns =
new() { "OrderDate", "Total", "CustomerName" };
public List<Order> GetOrders(string sortColumn)
{
if (!AllowedSortColumns.Contains(sortColumn))
throw new ArgumentException("Invalid sort column");
var sql = $"SELECT * FROM Orders ORDER BY {sortColumn}";
return context.Orders.FromSqlRaw(sql).ToList();
}
This exact pattern — dynamic sorting, dynamic filtering columns, dynamic table names in multi-tenant schemas — accounts for a disproportionate share of the raw-SQL injection issues found in EF Core codebases, precisely because LINQ has no clean built-in answer for identifier-level dynamism and developers improvise.
How Do You Audit an Existing EF Core Codebase for Injection Risk?
Start by grepping for the four method names that bypass parameterized LINQ: FromSqlRaw, ExecuteSqlRaw, Database.ExecuteSqlRaw, and any direct SqlCommand/SqlConnection usage inside a project that otherwise uses EF Core. In a mid-sized codebase (50,000–200,000 lines), this typically surfaces a call list in the dozens rather than hundreds, which makes manual review tractable in a day or two rather than requiring a full rewrite. For each call site, the check is mechanical: does every variable inside the SQL string argument come from a {n} placeholder or interpolated-string parameter, or is any of it string concatenation? Static analysis tools — including Roslyn analyzers like Microsoft.CodeAnalysis.NetAnalyzers' CA2100 rule and third-party SAST scanners — can flag concatenated raw SQL automatically, and running one of these in CI is more reliable than relying on code review alone, since this exact bug pattern is easy to miss in a large diff. Stored procedures called via FromSqlRaw deserve separate scrutiny: the procedure itself may build dynamic SQL internally with sp_executesql, which moves the injection surface into the database layer where application-level scanners won't see it.
What's the Fastest Way to Fix an Injection Vulnerability Once Found?
The fastest fix is almost always converting the raw SQL call back to parameterized form or, where feasible, back to LINQ entirely — not adding input sanitization or escaping on top of the existing string concatenation. Escaping single quotes or stripping "dangerous" characters is a common instinct but is not a reliable defense; encoding edge cases, second-order injection through stored data, and provider-specific escape rules make blocklist-based sanitization fragile in ways parameterization is not. For the common cases:
- Value-based
WHERE/SETclauses: switch toFromSqlInterpolated/ExecuteSqlInterpolatedor plain LINQ. - Dynamic sort/filter columns: allowlist against a fixed set of known-safe identifiers, as shown above.
- Dynamic table or schema names in multi-tenant systems: allowlist against the tenant's registered schema list, never build the name from unvalidated request data.
- Legacy
SqlCommandcode inside an EF Core project: migrate the query toDbContextwhere practical, or at minimum convert toSqlParameterobjects rather than string building.
Fixes at individual call sites typically take under an hour once the mechanism is understood; the real cost is discovery across a large codebase, which is why tooling coverage matters more than developer diligence for this class of bug.
How Safeguard Helps
Safeguard scans your C# and .NET dependency graph and source for exactly this pattern — raw SQL construction inside EF Core, ADO.NET, and Dapper codepaths — as part of continuous supply chain and application security monitoring, flagging concatenated FromSqlRaw/ExecuteSqlRaw calls and unparameterized SqlCommand usage before they reach production. Beyond static detection, Safeguard maps these findings to CWE-89 and OWASP A03:2021 for audit trails that satisfy SOC 2 and regulatory evidence requirements, so compliance teams don't have to manually reconcile scanner output with control frameworks. For teams managing legacy ADO.NET code alongside modern EF Core services, Safeguard's continuous monitoring catches injection risk introduced through dependency updates or reintroduced raw SQL during refactors — closing the exact gap where "the ORM protects us" assumptions break down in practice.