参数化查询是防御SQL注入的唯一安全方式,必须全程杜绝字符串拼接——无论输入是否经前端校验或模型验证,只要用户数据通过+或$""拼入SQL字面量,就会被数据库当作代码执行;SqlParameter或EF的FromSqlInterpolated等机制才能确保输入被严格视为纯数据。

直接用字符串拼接构造 SQL 语句,SqlCommand 就会变成攻击入口——这是 ASP.NET Core 中 SQL 注入漏洞最常见、也最容易被忽略的根源。
为什么 string + string 拼接 SQL 是危险操作
只要用户输入能进入 SQL 字符串字面量(比如 ' OR '1'='1),数据库就会把它当代码执行。哪怕你做了前端校验、后端空值检查,只要没切断“输入 → 字符串拼接 → 执行”这条链,就等于给攻击者留了后门。
- 拼接后的 SQL 会被数据库解析器当作完整语句重写,
WHERE条件可能被绕过,UNION SELECT可能拖出敏感表,;后还能追加任意语句 -
SqlParameter不是“可选优化”,而是唯一安全的传参方式;它让 SQL 引擎把参数值当纯数据处理,彻底剥离执行语义 - 即使用了 Entity Framework,手写
FromSqlRaw或ExecuteSqlRaw时如果插变量,一样中招——EF 不自动转义,只信任参数化形式
SqlParameter 的正确用法和常见错例
关键不是“用了没”,而是“怎么用”。很多开发者以为加了 @param 就安全了,结果还是拼接字符串塞进去。
- ✅ 正确:SQL 文本里只写
@username,所有参数通过command.Parameters.Add()或new SqlParameter()显式传入 - ❌ 错误:
"WHERE Name = '" + name + "'",哪怕 name 是从 ModelState 验证过的也不行 - ❌ 错误:
sql = $"SELECT * FROM Users WHERE Id = {id}";—— 数字类型也不能豁免,攻击者可用1; DROP TABLE Users--注入 - ⚠️ 注意:
AddWithValue有隐式类型推断风险,建议用Add(new SqlParameter("@name", SqlDbType.NVarChar).Value = name)显式指定类型和长度
EF Core 中哪些写法仍可能触发注入
EF Core 的 LINQ 查询天然参数化,但绕过 ORM 直接触碰 SQL 的地方必须格外小心。
-
FromSqlRaw("SELECT * FROM Posts WHERE Title LIKE '%{search}%'")——{search}是模板字符串插值,等同于拼接,绝对禁止 -
FromSqlInterpolated($"SELECT * FROM Posts WHERE Title LIKE {search}")—— 这个才安全,EF 会自动转为参数化查询 -
ExecuteSqlRaw("UPDATE Logs SET Message = '{msg}' WHERE Id = {id}")—— 同样危险,必须改用ExecuteSqlInterpolated或显式SqlParameter - 自定义存储过程调用时,如果传入的是拼接的 SQL 字符串而非参数,也会失效
模型绑定层该做什么,不该做什么
验证属性(如 [StringLength(50)]、[RegularExpression])能拦住部分恶意输入,但不能替代参数化查询——它们属于防御纵深,不是第一道闸门。
- 模型验证失败会返回 400,但攻击者可以跳过 API 层,直接发 POST 到暴露的端点,或伪造请求头绕过前端限制
-
[Range]对数字字段有效,但对字符串字段无法阻止' OR 1=1 -- - 真正该做的是:所有进数据库的字段,无论是否通过模型绑定,最终执行时都必须走
SqlParameter或 EF 的参数化接口 - 如果业务逻辑里需要动态构建 WHERE 条件(比如搜索过滤),优先用 LINQ 表达式树拼接,而不是字符串拼 SQL
参数化不是“加个 @ 符号就完事”,它要求整个数据流从接收、校验到执行,全程不出现任何字符串拼接路径。漏掉一个环节,前面所有防护都形同虚设。

















