flawopen.com/C# 中的 SQL Injection/EF Core FromSqlRaw
Entity Framework Core 的 FromSqlRaw 安全吗?
只要值与 SQL 文本分开传递,就可以安全使用。FromSqlRaw 接受原生 SQL;将不可信输入拼接或插入字符串会改变查询。应使用单独的参数实参,或将插值产生的 FormattableString 传给 FromSqlInterpolated(EF Core 7+ 也可用 FromSql)。可以把参数想成密封的值槽:数据库收到的是值,而不是新的 SQL 指令。
// 插值会先求值并生成字符串。 var sql = $"SELECT * FROM Users WHERE Id = {id}"; // FromSqlRaw 接收的是 SQL 文本,而不是 FormattableString。 var query = context.Users.FromSqlRaw(sql);
// EF Core 3–6:使用位置占位符并单独传值。 var byRawParameter = context.Users.FromSqlRaw( "SELECT * FROM Users WHERE Id = {0}", id); // EF Core 3+:将插值结果传给参数化 API。 var byInterpolation = context.Users.FromSqlInterpolated( $"SELECT * FROM Users WHERE Id = {id}"); // EF Core 7+:FromSql 也接受 FormattableString。 var byFromSql = context.Users.FromSql( $"SELECT * FROM Users WHERE Id = {id}");
容易忽略的关键点
FromSqlInterpolated 接收 FormattableString。EF Core 会读取其格式和参数,并将每个插值作为独立的数据库参数发送;这不是编译时拦截。EF Core 7 新增了 FromSql,对插值字符串采用相同的安全处理。更早版本应使用 FromSqlInterpolated。当 {0} 等占位符与单独传入的值匹配时,FromSqlRaw 也可以安全使用。如果先将插值结果存入 string,参数元数据就已经丢失。
参数绑定的是值,不是列名
数据库参数表示一个值,不能表示表名、列名、SQL 运算符或排序方向。如果查询结构必须动态变化,应在应用代码中从固定的允许列表选择标识符;不要尝试参数化标识符,也不要用字符过滤代替允许列表。同时检查授权和查询成本:合法列仍可能暴露敏感字段或触发昂贵扫描。
- ✓传递值时,使用直接插值的 FromSqlInterpolated/FromSql,或使用独立参数的 FromSqlRaw。
- ✓绝不要把拼接或插值后的用户数据传给 FromSqlRaw。不要先将 FormattableString 转换为 string。
- ✓动态标识符只能从固定允许列表中选择。另行确认用户是否有权访问该字段,并检查字段是否有合适索引。
- ✓如果 LINQ 能清楚表达查询,优先使用 LINQ;参数化能防止 SQL 语法注入,但不会执行授权或业务规则。
如何检查代码库
rg -n -g '*.cs' '(FromSqlRaw|FromSqlInterpolated|FromSql)\s*\(' .常见问题
如果 SQL 使用占位符,并将值作为独立实参传入,则安全,例如 FromSqlRaw("... {0}", value)。不要先将值拼接或插值进 SQL 字符串。
它接收 FormattableString,可以分别保留格式和值。EF Core 会将每个值转换为数据库参数。如果先赋给 string,或传给 FromSqlRaw,这种保护就会消失。
不能。参数只表示值。对于动态标识符,应将用户选项映射到固定的已知标识符允许列表,并检查授权和查询成本。