防SQL注入的底线是CommandText必须为静态字符串且用户输入全走Parameters集合;动态SQL结构(如表名、排序字段)须白名单校验;EF Core中FromSqlRaw需用FromSqlInterpolated或显式参数化。

必须用 SqlParameter 或 OracleParameter,不能拼接字符串
在 .NET 8 中,防 SQL 注入的底线是:所有用户输入进 SQL 的地方,CommandText 必须是静态字符串,参数必须走 Parameters 集合。哪怕只有一处用 string.Format、$"" 或 + 拼接,整个防护就失效。
常见错误写法:
string sql = $"SELECT * FROM users WHERE id = {id}"; // ❌ 单引号、分号、UNION 全部放行
cmd.CommandText = sql;
正确写法(SQL Server):
cmd.CommandText = "SELECT * FROM users WHERE id = @id";
cmd.Parameters.Add(new SqlParameter("@id", id)); // 名称必须和 SQL 中 @id 完全一致
正确写法(Oracle):
cmd.CommandText = "SELECT * FROM users WHERE id = :id";
cmd.Parameters.Add(new OracleParameter("id", id)); // 注意:构造函数传的是 "id",不是 ":id"
-
OracleParameter构造函数第一个参数是参数名(不含冒号),SQL 中占位符必须写:id,大小写敏感 - SQL Server 的
SqlParameter名称带@,但构造函数里也传"@id",不是"id" - 位置绑定(
?)在 Oracle 中对 PL/SQL 块或 REF CURSOR 支持差,优先用命名绑定
动态表名、列名、ORDER BY 字段必须白名单校验
SqlParameter 只能保护“值”,不能保护 SQL 结构。一旦用户输入出现在语法位置(如 FROM {table}、ORDER BY {col}),参数化完全无效,过滤关键词也拦不住。
必须做两层校验:
- 格式正则:用
^[a-zA-Z][a-zA-Z0-9_]{0,29}$匹配标识符,拒绝点号、双引号、美元符 - 存在性验证:查
ALL_TABLES(Oracle)或sys.tables(SQL Server),确认该名称真实属于当前 schema
示例(排序字段):
var allowedSortFields = new[] { "name", "created_at", "status" };
if (!allowedSortFields.Contains(sortField, StringComparer.OrdinalIgnoreCase))
throw new ArgumentException("Invalid sort field");
cmd.CommandText = $"SELECT * FROM orders ORDER BY {sortField}"; // ✅ 已白名单,可拼接
别信全局关键词拦截中间件
在 .NET 8 Web API 里加个 OnActionExecuting 拦截 select、union、-- 等词,看起来很勤快,实际作用极小:
- URL 编码绕过:
%27(单引号)、%6f%72(or)不会被原始字符串匹配到 - 大小写变形:
SeLeCt、UNI/**/ON轻松逃逸 - 根本拦不住结构注入:比如
id=1; DROP TABLE users--在 POST body 里,中间件读不到(已绑定为 model) - 误杀率高:用户昵称 “O'Reilly”、“Master Chief” 直接 400
如果真要用,必须:
- 先调
WebUtility.UrlDecode()+WebUtility.HtmlDecode() - 用
\b(select|union|exec|xp_cmdshell|;|--|/\*|#)\b正则,带词边界 - 跳过 ASP.NET 内置字段:
__RequestVerificationToken、__viewstate(忽略大小写) - 仅作日志+告警,绝不阻断——它只是发现漏迁参数化查询的探针
EF Core 里也要小心 FromSqlRaw 和字符串插值
EF Core 的 FromSqlRaw 和 ExecuteSqlRaw 是高危接口,默认不参数化。以下写法等同裸奔:
context.Users.FromSqlRaw($"SELECT * FROM users WHERE name = '{name}'"); // ❌
正确做法只有两种:
- 用
FromSqlInterpolated(推荐):
context.Users.FromSqlInterpolated($"SELECT * FROM users WHERE name = {name}"); // ✅ 自动转为参数
- 显式传参(兼容老版本):
context.Users.FromSqlRaw("SELECT * FROM users WHERE name = {0}", name); // ✅ 位置参数
注意:FromSqlInterpolated 仅支持 C# 插值语法({name}),不支持 $"..." 里套 {};变量名不能是表达式(如 {user.Name} 不行,得先赋值给局部变量)。
最易忽略的一点:EF Core 的 Where 表达式树(.Where(x => x.Name == name))是安全的,但一旦进了 FromSqlRaw,就彻底退出 EF 的参数化保护机制——这里没有魔法,只有你写的 SQL 和你传的参数。











