唯一真正可靠的防线是参数化查询,其他如关键词过滤易被绕过、误杀率高且维护成本大;必须全程贯彻参数化,从应用层到存储过程均需杜绝字符串拼接。

直接用参数化查询,别碰字符串拼接——这是唯一真正可靠的防线。其他所有“过滤关键词”“拦截非法字符”的方案,要么漏报要么误杀,还会让你在后期维护中反复踩坑。
为什么不能靠关键词过滤防SQL注入
关键词过滤(比如拦截 select、drop、--)本质是用正则对抗人脑,而攻击者永远比你多想一步。一个 /**/UNION/**/SELECT 就能绕过大多数黑名单;Unicode 编码、URL 编码、空格替换、大小写混用也都能轻松逃逸。
- 误杀率高:用户昵称叫
Truncate或搜索词含master就被拦,客服投诉翻倍 - 漏报严重:攻击者用
1;WAITFOR DELAY '0:0:5'--做盲注,关键词里根本没“危险词” - 维护成本爆炸:每次加新字段、改业务逻辑,都要同步更新过滤规则,没人敢动
ADO.NET 中必须用 SqlParameter 替代字符串拼接
只要涉及 SqlCommand,就绝不能出现 "WHERE Name = '" + name + "'" 这种写法。参数化不是“可选优化”,是强制契约。
-
SqlParameter会把值作为纯数据传给 SQL Server,服务端根本不解析它为语句的一部分 - 即使
name是"admin' OR 1=1--",最终执行的也是WHERE Name = N'admin'' OR 1=1--'—— 单引号被自动转义,逻辑不变 - 注意:必须用
cmd.Parameters.Add("@name", SqlDbType.NVarChar).Value = name;,别用AddWithValue,它可能推断错类型导致隐式转换失败
EF Core 中坚持用 LINQ,慎用 FromSqlRaw
EF Core 的 IQueryable 查询天然参数化,但一旦调用 FromSqlRaw 或 ExecuteSqlRaw,就回到了“手写 SQL”的风险区。
- 安全写法:
context.Users.Where(u => u.Name.Contains(name)).ToList()→ 自动生成带@__name_0参数的 SQL - 危险写法:
context.Users.FromSqlRaw("SELECT * FROM Users WHERE Name LIKE '%" + name + "%'")→ 直接拼接,等同裸奔 - 真要用原始 SQL:必须显式传参,
context.Users.FromSqlRaw("SELECT * FROM Users WHERE Name LIKE {0}", $"%{name}%")不行;正确是FromSqlRaw("SELECT * FROM Users WHERE Name LIKE @name", new SqlParameter("@name", $"%{name}%"))
全局过滤器(如 ActionFilter)对 SQL 注入无效
在 MVC 5 里有人写 SqlInjectAttribute 拦截控制器参数,这属于典型的方向错误——它拦的是 HTTP 请求参数,不是数据库执行时的语句上下文。
- 无法覆盖所有入口:AJAX 提交、API Controller、后台任务、SignalR 消息都绕过 MVC Filter
- 混淆责任边界:参数校验该做 XSS 或业务规则检查,SQL 安全该由数据访问层兜底
- 性能损耗:每个请求都遍历所有参数做字符串扫描,而真正需要防护的只有那几处
SqlCommand或FromSqlRaw
最易被忽略的一点:存储过程本身不免疫 SQL 注入。如果存储过程中用了 EXEC(@sql) 动态拼接,哪怕调用它的 C# 代码用了参数,照样中招。防御必须贯穿整条数据链路,从 Controller 到 DAL 再到 T-SQL 内部,每一环都得守住参数化这条线。











