必须用SqlParameter替换字符串拼接,因直接拼接用户输入(如"WHERE name = '"+txtName.Text+"')会导致SQL注入;参数化查询将值与SQL结构分离,由数据库驱动安全绑定,且所有执行方法(ExecuteNonQuery/Scalar/Reader)及EF Core的FromSqlRaw均需严格参数化。

用 SqlParameter 替换字符串拼接,这是唯一靠谱的做法
直接拼接用户输入到 SQL 字符串里(比如 "SELECT * FROM Users WHERE name = '" + txtName.Text + "'")等于给攻击者递刀。C# 本身不自动防注入,必须显式用参数化查询——不是“推荐”,是“必须”。SqlCommand 的 Parameters 集合才是安全边界,SQL Server(或其他数据库驱动)会把参数值当纯数据处理,不参与语法解析。
实操要点:
-
SqlParameter的Value属性要设为实际值,别传null或空字符串后靠默认逻辑兜底 - 参数名必须带
@前缀(如@username),且和 SQL 字符串里的占位符严格一致(大小写敏感) - 避免用
string.Format或$""拼接 SQL,哪怕只拼表名或列名——这些不能参数化,得走白名单校验或SqlIdentifier类型转换
别在 SqlCommand.CommandText 里写死参数名,动态生成时尤其危险
常见错误是用字符串拼出带参数的 SQL,比如:"UPDATE " + tableName + " SET value = @" + paramName。这看起来用了参数,但表名、列名、操作类型(INSERT/UPDATE)仍由用户控制,照样可触发注入(例如把 tableName 设为 "Users; DROP TABLE Orders--")。
正确做法:
- 表名/列名等标识符必须来自预定义白名单,或用
System.Data.Common.DbProviderFactories提供的转义方法(如 SQL Server 的QUOTENAME()函数) - 如果真要动态构建语句,优先用 Dapper 或 Entity Framework Core 这类 ORM,它们对标识符有额外防护层
- 绝不要信任前端传来的任意字符串作为 SQL 结构的一部分
ExecuteScalar 和 ExecuteReader 同样需要参数化,不是只有 ExecuteNonQuery 才要
有人以为只有增删改才需防注入,查数据就安全——错。攻击者完全可以通过 UNION SELECT 把其他表数据拖出来,或者用 WAITFOR DELAY '0:0:5' 实施盲注。只要 SQL 字符串含用户输入,不管用哪个执行方法,都得上参数。
典型风险点:
-
SELECT COUNT(*) FROM Logs WHERE ip = '" + userInput + "'→ 攻击者输"127.0.0.1' UNION SELECT password FROM Users--" - 用
ExecuteScalar获取单值时,若 SQL 含拼接,返回的可能根本不是你想要的字段 - 即使加了
WHERE 1=0之类限制,也不能替代参数化——条件本身也可能被绕过
EF Core 的 FromSqlRaw 不自动参数化,容易误以为安全
EF Core 默认的 LINQ 查询是安全的,但一旦调用 FromSqlRaw 或 ExecuteSqlRaw,就退回到原始 SQL 场景。很多人写 context.Users.FromSqlRaw("SELECT * FROM Users WHERE name = {0}", name),以为大括号能防注入——其实不能,它只是简单字符串替换,不是参数绑定。
必须改成:
context.Users.FromSqlRaw("SELECT * FROM Users WHERE name = {0}", <font color="#0066cc">@name</font>)或更稳妥地用命名参数:
context.Users.FromSqlRaw("SELECT * FROM Users WHERE name = @name", new SqlParameter("@name", name))注意:{0} 是旧版 EF 的写法,EF Core 中仅支持 @param 形式;用错格式等于裸奔。
真正难的不是写对一行参数,而是所有入口——Web API 参数、表单提交、URL 查询字符串、甚至后台定时任务读取的配置项——只要进 SQL,就得过参数化这一关。漏掉一个,整套防护就失效。


















