FromSqlRaw和ExecuteSqlRaw必须手动参数化,FromSqlInterpolated仅安全处理纯变量插值;表名、列名、排序方向等结构部分无法参数化,须用白名单校验。

FromSqlRaw和ExecuteSqlRaw必须手动参数化
这两个方法不防注入,传进去的字符串原样发给数据库驱动。哪怕只拼一个字段值,只要用了+或$""插入用户输入,就立刻破防。
-
FromSqlRaw($"SELECT * FROM Users WHERE Name = '{name}'")→ 危险,'admin' OR 1=1 --在C#层已混入SQL -
FromSqlRaw("SELECT * FROM Users WHERE Name = " + name)→ 同样危险,拼接早于EF执行 -
FromSqlRaw("WHERE Status = {0}", "Active")→ 安全,EF把{0}当参数占位符绑定 -
FromSqlRaw("WHERE Name = @name", new SqlParameter("@name", name))→ 安全,显式构造参数对象
FromSqlInterpolated只安全处理纯变量插值
它确实自动参数化,但仅限未经过C#运算的原始变量。一旦涉及拼接、表达式或字符串组合,就失去防护能力。
- ✅ 安全:
context.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE Name = {name}") - ✅ 安全:
context.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE CreatedAt > {DateTime.UtcNow.AddDays(-7)}") - ❌ 危险:
context.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE Name LIKE {name + "%"}")→name + "%"是C#表达式,结果整体被当参数,语义错乱 - ❌ 危险:
context.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE {whereClause}")→whereClause是字符串变量,整个被当参数值,WHERE条件消失
表名、列名、ORDER BY等结构部分无法参数化
数据库引擎根本不支持参数化标识符(identifier),FromSqlRaw("SELECT * FROM {0}", tableName)语法非法,EF也不会校验或转义。
- 动态字段名必须白名单校验:
new[] { "Name", "Email", "CreatedAt" }.Contains(sortField),否则直接拒绝 - 排序方向(ASC/DESC)必须显式判断:
if (sortDir != "ASC" && sortDir != "DESC") throw new ArgumentException() - 禁止用
SqlParameter包裹字段名——它会被当作文本值,导致ORDER BY 'Name',排序失效 - 禁止用
string.Replace或正则“过滤关键词”,"oR"、换行、注释符都能绕过
动态LINQ表达式树也可能引入列名注入
用Expression.Property或GetProperty反射取字段时,若字段名来自用户输入且未经白名单控制,等于把列名直接交给SQL生成器。
- ❌ 危险:
var prop = u.GetType().GetProperty(fieldName); Expression.MakeMemberAccess(param, prop)→fieldName未校验 - ✅ 安全:先查白名单,再反射:
if (!allowedFields.Contains(fieldName)) throw ... - 避免调用
.ToString()或Expression.ToString()——这会让表达式树提前降级为字符串,绕过EF的参数化机制 - 慎用
Expression.AndAlso拼接后编译成Func:会导致内存过滤而非数据库下推,且字段名若动态拼接仍可能引入列名注入
动态SQL注入最常被忽略的点,不是“有没有参数化”,而是“哪里根本不能参数化”——表名、列名、排序方向、GROUP BY字段这些SQL结构部分,必须靠白名单卡死,任何试图用参数化绕过的做法,都在语法或语义层面失效。











