唯一可靠的方式是全程使用 sqlparameter 绑定所有用户输入并显式指定类型与长度;addwithvalue 因自动推断类型长度易致索引失效和截断;存储过程调用必须设 commandtype.storedprocedure 且硬编码名称;order by 等结构无法参数化,须白名单校验。

唯一可靠的方式是全程使用 SqlParameter 绑定所有用户输入,且必须显式指定类型与长度;字符串拼接、AddWithValue、存储过程名拼接、动态标识符插值,全都不防注入。
为什么 AddWithValue 在 varchar 场景下很危险
它会根据传入的字符串值自动推断 SqlDbType 和 Size,比如传 "admin" 可能被推成 VarChar(5)。一旦后续查询走索引,SQL Server 可能因隐式转换放弃索引,性能骤降;更糟的是,若字段实际是 VarChar(50),而参数被截成 VarChar(5),某些驱动下还会触发计划缓存污染或意外匹配。
- 正确写法:
cmd.Parameters.Add(new SqlParameter("@name", SqlDbType.NVarChar) { Value = userName ?? (object)DBNull.Value, Size = 100 }); - 空值必须用
DBNull.Value,不能传null或空字符串 - 日期统一用
SqlDbType.DateTime2,避免DateTime在旧版本中精度丢失
存储过程调用不设 CommandType.StoredProcedure 就等于没防
很多开发者以为只要写了 "EXEC GetUserById @id" 就安全了,其实只要没设 cmd.CommandType = CommandType.StoredProcedure,ADO.NET 就按普通 SQL 执行,参数会被忽略,@id 直接当字面量丢进解析器——此时和字符串拼接毫无区别。
- 必须写死过程名:
cmd.CommandText = "GetUserById";,绝不能"EXEC " + procName - 过程内部如果用了
EXEC(@sql)或sp_executesql拼接,外部再规范也白搭 - EF Core 的
FromSqlRaw同理:不带SqlParameter参数,或用了$"SELECT {input}"插值,风险立刻回归
ORDER BY、表名、列名无法参数化,白名单校验是唯一出路
SQL Server 明确不支持 ORDER BY @sortColumn 这类语法,报错是“必须是表达式”,不是“参数未定义”。试图用 QUOTENAME() 或正则过滤单引号,都属于伪防护——Unicode 编码、注释绕过、大小写混用可轻松击穿。
- 只允许硬编码映射:
var sortField = userSort switch { "name" => "user_name", "date" => "created_at", _ => "id" }; - 分表场景下,表名必须来自预置列表,不能靠
string.Format("log_{0}", year)拼接 -
FromSqlInterpolated只对变量插值做参数化,ORDER BY {sort}中的{sort}仍是直接拼接,不可信
最常被忽略的一点:参数化只保护「值」,不保护「结构」;而生产环境里,90% 的绕过都发生在排序、分页、多租户表路由这些看似“不重要”的动态位置。别信任何“加了参数就万事大吉”的直觉。










