LINQ to SQL/EF 默认参数化查询天然防SQL注入,但动态排序需白名单+显式分支,FromSqlRaw等逃逸API必须参数化,手动拼接CommandText会退化为裸ADO.NET风险。

LINQ to SQL 查询本身不会生成恶意 SQL,只要你没主动绕过它的表达式树机制——但很多人在动态排序、条件拼接、原始 SQL 逃逸时直接掉进坑里。
Where 条件里用变量,天然安全
只要写的是 Where(x => x.Email == userInput) 这类标准表达式,EF 或 LINQ to SQL 会把 userInput 编译为参数化 SQL(如 WHERE [Email] = @p0),值走 ADO.NET 参数绑定,攻击者输 "admin' OR 1=1 --" 也只会当字符串查,不会触发语法解析。
常见错误现象:有人看到登录失败日志里出现 ' OR '1'='1 就慌,其实那是正常参数值,不是执行了注入。
- ✅ 安全:所有字段名、操作符、逻辑结构都由 C# 表达式静态定义
- ❌ 危险:一旦换成
.Where($"x.Email == '{userInput}'")(字符串插值)或.AsEnumerable().Where(...)后再过滤,就脱离了 SQL 生成阶段,但也不引入注入——只是性能问题
OrderBy 动态列名会编译失败,别硬绕
OrderBy(x => x.GetType().GetProperty(sortField).GetValue(x)) 这种反射写法在 LINQ to SQL 下根本跑不通,因为表达式树无法翻译成 SQL;强行用 AsEnumerable() 拉到内存再排序,虽不报错,但数据量一大就卡死。
真正该做的,是白名单校验 + 显式分支:
var query = context.Users.AsQueryable();
switch (sortField.ToLower())
{
case "email": query = query.OrderBy(x => x.Email); break;
case "name": query = query.OrderBy(x => x.Name); break;
default: query = query.OrderBy(x => x.Id); break;
}- ⚠️ 切忌用
Expression.Parameter或LambdaExpression.Compile()拼表达式树传给OrderBy,容易触发运行时异常,且无实际安全增益 - ? 白名单必须硬编码或从配置读取可信枚举,不能依赖数据库字段元数据动态加载(除非你确认元数据来源绝对可信)
FromSqlRaw 和 ExecuteSqlRaw 是唯一“合法出口”,但必须参数化
这两个 API 是 LINQ to SQL / EF Core 提供的“逃逸通道”,不是后门。它们支持位置占位符 {0}、{1} 或命名参数(如 @email),但拒绝字符串插值和拼接。
典型错误现象:日志里看到 SELECT * FROM Users WHERE Email = 'xxx' 直接出现在 SQL Profiler 中,说明用了 $"..." 插值,参数没生效。
- ✅ 正确:
context.Users.FromSqlRaw("SELECT * FROM Users WHERE Email = {0}", email) - ✅ 正确:
context.Database.ExecuteSqlRaw("UPDATE Logs SET Status = @status WHERE Id = @id", new SqliteParameter("@status", status), new SqliteParameter("@id", id)) - ❌ 危险:
context.Users.FromSqlRaw($"SELECT * FROM Users WHERE Email = '{email}'")—— 单引号、分号、--全部照单执行
复杂动态查询别硬扛,分层处理更稳
当查询条件多变(比如后台搜索页有 10 个可选字段)、又必须走数据库时,硬写一堆 if + Where 链容易漏判,也难维护。这时候不如拆开:
- 前端传来的字段名/操作符(如
["Name", "Contains"])先过白名单校验 - 值部分一律走参数化,不参与 SQL 字符串构造
- 实在绕不开动态表名或 Schema,用存储过程封装,且存储过程中也必须用
sp_executesql+ 参数化,而非EXEC(@sql)
最容易被忽略的一点:即使你 100% 没用 FromSqlRaw,只要在某个角落调用了 context.Database.GetDbConnection().CreateCommand() 并手动拼 CommandText,整条链路就立刻退化成裸 ADO.NET —— 那时候,Parameters.Add() 你得自己写对,漏一个就崩。

















