Description
SQL injection occurs when untrusted values are concatenated directly into SQL command text and interpreted as syntax rather than data. An attacker can insert conditions, additional queries, or comments to bypass authentication or read, modify, and delete unauthorized data.
Input validation and SQL escaping do not replace parameter binding. String-literal rules and character encodings vary between databases, so a helper named sanitize or escape does not itself establish that the input is safe.
Remediation
Keep the SQL structure fixed and bind every untrusted value separately from command text.
- In ADO.NET, add provider-appropriate
DbParametervalues and specify types and sizes where possible. - In Dapper, pass values through anonymous objects or
DynamicParameters. - In EF Core, prefer
FromSql,ExecuteSql, orSqlQuery, which safely parameterize interpolated values. If a*RawAPI is needed, keep SQL fixed and pass placeholder values separately. - SQL identifiers such as table names, column names, and sort directions cannot be bound as value parameters. Limit user choices to a small allow-list and map each to a developer-authored literal.
- Give the database account only the permissions needed for its operations.
Examples
Before
This code inserts a request value directly into SQL text.
using System.Data;
using Dapper;
using Microsoft.AspNetCore.Mvc;
public sealed class UsersController
{
public object Search(
[FromQuery] string username,
IDbConnection dapperConnection)
{
var sql = $"SELECT * FROM Users WHERE Username = '{username}'";
return dapperConnection.QueryFirstOrDefault(sql);
}
}
After
Keep SQL and values separate in ADO.NET and Dapper. AddWithValue also separates values from command text; this example specifies the type and size to reduce inference ambiguity and performance problems.
using System.Data;
using Dapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
public sealed class UsersController
{
public object Search(
[FromQuery] string username,
SqlConnection sqlConnection,
IDbConnection dapperConnection)
{
const string sql =
"SELECT * FROM Users WHERE Username = @username";
using var command = new SqlCommand(sql, sqlConnection);
command.Parameters
.Add("@username", SqlDbType.NVarChar, 128)
.Value = username;
using var reader = command.ExecuteReader();
return dapperConnection.QueryFirstOrDefault(
sql,
new { username });
}
}
EF Core's interpolation-aware APIs turn interpolated values into parameters. Raw APIs can also receive values separately.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
public sealed class User
{
public int Id { get; set; }
public string Username { get; set; } = "";
}
public sealed class UsersController
{
public object Search(
[FromQuery] string username,
DbSet<User> users)
{
var parameterized = users.FromSql(
$"SELECT * FROM Users WHERE Username = {username}");
var rawWithSeparateValue = users.FromSqlRaw(
"SELECT * FROM Users WHERE Username = {0}",
username);
return new { parameterized, rawWithSeparateValue };
}
}
When an SQL identifier must be dynamic, map the selection to a fixed literal instead of concatenating raw input.
using System.Data;
using Dapper;
using Microsoft.AspNetCore.Mvc;
public sealed class ReportsController
{
public object List(
[FromQuery] string sort,
IDbConnection connection)
{
var orderBy = sort switch
{
"name" => "Name",
"created" => "CreatedAt",
_ => "Id"
};
var sql = "SELECT * FROM Reports ORDER BY " + orderBy;
return connection.Query(sql);
}
}
References
- OWASP SQL Injection Prevention Cheat Sheet
- OWASP Query Parameterization Cheat Sheet
- Entity Framework Core: SQL Queries
- ADO.NET: Configuring Parameters and Parameter Data Types
- Dapper documentation
- OWASP ASVS 5.0.0, V1.2.4
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command
- OWASP Top 10:2025 A05 - Injection