参数化查询必须使用@param占位符,确保用户输入仅作为值不参与sql解析;禁止字符串拼接、动态sql及非白名单的标识符拼接;存储过程、ef core和dapper均需严格遵循参数化规范。

SqlCommand 参数化查询必须用 @param 占位符,不能拼字符串
直接把用户输入塞进 SQL 字符串里(比如 "SELECT * FROM Users WHERE name = '" + input + "'")等于给攻击者开后门。SQL Server 会把引号、分号、UNION SELECT 全当语法执行,参数化查询的核心就是让输入**永远只被当作值**,不参与 SQL 解析。
正确做法是用 @ 开头的命名参数,再通过 Parameters.Add() 绑定值:
string sql = "SELECT * FROM Users WHERE email = @email AND status = @status";
using var cmd = new SqlCommand(sql, conn);
cmd.Parameters.Add(new SqlParameter("@email", userEmail));
cmd.Parameters.Add(new SqlParameter("@status", "active"));-
@email和@status必须和 SQL 字符串里完全一致(包括大小写) - 不要用
?占位符(那是 ODBC 或 SQLite 的写法,SQL Server 不认) - 避免用
Parameters.AddWithValue()—— 它会自动推断类型,可能把int推成decimal,导致索引失效或隐式转换错误
存储过程调用也得用参数,别在 SP 内部拼接
有人以为“用了存储过程就安全了”,其实不然。如果在存储过程中用 EXEC(@sql) 或 sp_executesql 拼接用户输入,照样中招。真正安全的是:C# 层传参进去,SP 内部只用参数变量做条件判断。
例如 SP 定义为:
CREATE PROC GetUserByPhone @phone NVARCHAR(20) AS SELECT * FROM Users WHERE phone = @phone
C# 调用时:
var cmd = new SqlCommand("GetUserByPhone", conn) { CommandType = CommandType.StoredProcedure };
cmd.Parameters.Add(new SqlParameter("@phone", userPhone));- 务必设
CommandType = CommandType.StoredProcedure,否则 SQL Server 当成普通 SQL 执行 - SP 内部禁止出现
CONCAT、+连接用户输入、或QUOTENAME()后再拼接——那只是“看起来像防注入”,实际仍可能绕过
动态列名/表名不能参数化,得靠白名单或 QUOTENAME
参数化只适用于**值(value)**,不适用于对象名(如列名、表名、排序字段)。如果你要根据用户选择动态切换 ORDER BY 字段,@sortColumn 是无效的:
/* ❌ 错误:SQL Server 不允许参数化标识符 */ SELECT * FROM Logs ORDER BY @sortColumn
可行方案只有两个:
- 硬编码白名单:
switch (userSort) { case "created": order = "created_at"; break; ... } - 用
QUOTENAME()包裹后再拼(仅限 T-SQL 层):"ORDER BY " + QUOTENAME(@rawSort),但注意 C# 层仍需先校验@rawSort是否在允许范围内,不能无条件信任
任何试图用反射、表达式树或 DataTable.Columns 动态生成列名的操作,都得走白名单兜底——没例外。
Entity Framework 也得小心 FromSqlRaw 和字符串插值
EF Core 默认 LINQ 查询是安全的,但一旦用到 FromSqlRaw 或 ExecuteSqlRaw,就重回手写 SQL 场景。下面这段看着像参数化,实则危险:
/* ❌ 错误:字符串插值在编译期就拼好了,参数化失效 */
context.Users.FromSqlRaw($"SELECT * FROM Users WHERE name = '{name}'");正确写法必须显式传参:
context.Users.FromSqlRaw("SELECT * FROM Users WHERE name = {{0}}", name); // EF Core 5+
// 或更推荐:
context.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE name = {name}");-
FromSqlInterpolated是 EF Core 提供的安全插值方式,它会自动转成参数化 - 绝对不要在
FromSqlRaw里用$"..."插入变量,哪怕加了@前缀也没用 - 第三方库如 Dapper 同理:
connection.Query<user>("SELECT * FROM Users WHERE id = @id", new { id = userId })</user>是安全的;new { sql = $"WHERE id = {userId}" }就是自毁
最常被忽略的一点:参数化能防注入,但防不了逻辑漏洞(比如用户越权查别人数据)、也防不了错误信息泄露(比如把 SQL 异常直接返回前端)。真正的防护是分层的——参数化只是第一道,后面还得有最小权限账号、输入长度/格式校验、异常屏蔽。










