必须用sqlparameter绑定所有用户输入,严禁字符串拼接;因参数化使数据库将输入视为纯数据而非sql指令,彻底阻断' or 1=1 --等注入攻击。

直接说结论:所有用户输入进 SQL 的地方,必须用 SqlParameter 绑定,字符串拼接(包括 $""、string.Format、+)一律禁止上线——这不是“建议”,是生产环境红线。
为什么万能密码 ' OR 1=1 -- 能绕过登录
典型错误写法:"SELECT * FROM Users WHERE UserName = '" + txtUser.Text + "' AND Password = '" + txtPass.Text + "'"。用户输 admin'--,SQL 就变成:SELECT * FROM Users WHERE UserName = 'admin'--' AND Password = '',后面密码条件被注释掉,直接登录成功。
更危险的是,攻击者还能输 admin'; DROP TABLE Users; --,只要数据库账号有删表权限,整张表就没了。
这类问题不是“前端校验没做好”,而是后端根本没做数据与 SQL 指令的分离。参数化查询让数据库引擎把 @user 当纯值处理,哪怕值是 "; DROP TABLE Users; --,也只会查一个叫这个名字的用户,不会执行任何命令。
SqlCommand.Parameters.Add() 的三种写法怎么选
安全的核心不是“用了参数”,而是“怎么传、怎么设”。常见坑集中在类型推断和空值处理上:
-
cmd.Parameters.AddWithValue("@name", userName):方便但危险。传null可能推成SqlDbType.Int,导致后续WHERE status = @status比较失败;传短字符串可能推成过小的VarChar(5),索引失效 -
cmd.Parameters.Add(new SqlParameter("@name", SqlDbType.NVarChar) { Value = userName }):推荐。显式指定类型和长度(如Size = 50),避免隐式转换,也兼容null(Value直接赋(object)userName ?? DBNull.Value) -
cmd.Parameters.Add("@name", SqlDbType.NVarChar).Value = userName:语法糖,本质同上,可读性略差但无实质风险
动态条件和 IN 列表怎么安全写
这里最容易手滑拼 SQL。比如搜索带“状态”“部门”“时间范围”的组合查询,或批量查 id IN (1,2,3...):
动态 WHERE 条件不能拼整个 SQL 字符串,而应拼结构片段 + 固定参数名:
var sql = "SELECT * FROM Orders WHERE 1=1";
var parameters = new List<sqlparameter>();
<p>if (!string.IsNullOrEmpty(status)) {
sql += " AND Status = @status";
parameters.Add(new SqlParameter("@status", SqlDbType.VarChar, 20) { Value = status });
}</p>
<p>if (startTime != null) {
sql += " AND CreatedTime >= @startTime";
parameters.Add(new SqlParameter("@startTime", SqlDbType.DateTime2) { Value = startTime });
}</p></sqlparameter>
IN 列表不能写成 "WHERE id IN (" + string.Join(",", ids) + ")"。SQL Server 不支持数组参数,正确做法是生成固定占位符:
- ID 数量 ≤ 2000:循环构造
@id1,@id2…,再用Add()绑定每个值 - ID 数量超限:改用
DataTable+SqlDbType.Structured(表值参数),或先写入临时表
存储过程和 EF Core 里那些“看起来安全”的陷阱
存储过程不是免检区。错在两处:
- C# 层调用时没设
cmd.CommandType = CommandType.StoredProcedure,结果 SQL Server 把"GetUserById"当普通 SQL 执行,参数全被忽略 - 存储过程内部用了
EXEC(@sql)或sp_executesql拼接用户输入——外部参数化白搭,漏洞仍在 SP 内部
EF Core 同理:FromSqlRaw("SELECT * FROM Users WHERE Name = {0}", name) 是错的;FromSqlRaw("SELECT * FROM Users WHERE Name = @name", new SqlParameter("@name", name)) 才对。而 ORDER BY 字段、表名、列名永远不能参数化,必须用白名单硬编码(如 switch(sortBy) { case "name": order = "Name"; break; })。
最常被忽略的一点:参数化只保“值”,不保“结构”。你传 @tableName 进去想动态切表?SQL Server 直接报错。这种需求只能靠白名单或 QUOTENAME()(且仅限可信上下文),绝不能依赖正则过滤“删掉分号”。










